Concurrency utilities in Java provide a robust framework for managing multiple threads and ensuring thread safety. These utilities are part of the java.util.concurrent
package and offer various classes and interfaces to handle concurrent programming more efficiently.
Key Concepts
- Executor Framework: Manages a pool of worker threads, simplifying the execution of asynchronous tasks.
- Concurrent Collections: Thread-safe versions of standard collections like
ConcurrentHashMap
,CopyOnWriteArrayList
, etc. - Locks: More flexible and powerful alternatives to synchronized blocks, such as
ReentrantLock
. - Atomic Variables: Provide lock-free thread-safe operations on single variables.
- Synchronizers: Utilities like
CountDownLatch
,CyclicBarrier
,Semaphore
, andExchanger
to manage thread coordination.
Executor Framework
The Executor framework provides a way to decouple task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc.
Example: Using ExecutorService
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); for (int i = 0; i < 5; i++) { Runnable worker = new WorkerThread("" + i); executor.execute(worker); } executor.shutdown(); while (!executor.isTerminated()) { } System.out.println("Finished all threads"); } } class WorkerThread implements Runnable { private String command; public WorkerThread(String s) { this.command = s; } @Override public void run() { System.out.println(Thread.currentThread().getName() + " Start. Command = " + command); processCommand(); System.out.println(Thread.currentThread().getName() + " End."); } private void processCommand() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }
Explanation
- ExecutorService: Manages a pool of threads.
- Executors.newFixedThreadPool(3): Creates a thread pool with 3 threads.
- executor.execute(worker): Submits tasks for execution.
- executor.shutdown(): Initiates an orderly shutdown.
- executor.isTerminated(): Checks if all tasks have completed.
Concurrent Collections
Concurrent collections are designed to be used in a multi-threaded environment without the need for external synchronization.
Example: Using ConcurrentHashMap
import java.util.concurrent.ConcurrentHashMap; public class ConcurrentHashMapExample { public static void main(String[] args) { ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); map.put("One", 1); map.put("Two", 2); map.put("Three", 3); map.forEach((key, value) -> System.out.println(key + " = " + value)); } }
Explanation
- ConcurrentHashMap: A thread-safe version of
HashMap
. - map.put(): Adds key-value pairs to the map.
- map.forEach(): Iterates over the map entries.
Locks
Locks provide more extensive operations than synchronized blocks.
Example: Using ReentrantLock
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockExample { private final Lock lock = new ReentrantLock(); public void performTask() { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " is performing task"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) { ReentrantLockExample example = new ReentrantLockExample(); Runnable task = example::performTask; Thread t1 = new Thread(task); Thread t2 = new Thread(task); t1.start(); t2.start(); } }
Explanation
- ReentrantLock: A reentrant mutual exclusion lock.
- lock.lock(): Acquires the lock.
- lock.unlock(): Releases the lock.
Atomic Variables
Atomic variables provide lock-free thread-safe operations on single variables.
Example: Using AtomicInteger
import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerExample { private AtomicInteger counter = new AtomicInteger(0); public void increment() { counter.incrementAndGet(); } public int getCounter() { return counter.get(); } public static void main(String[] args) { AtomicIntegerExample example = new AtomicIntegerExample(); Runnable task = example::increment; Thread t1 = new Thread(task); Thread t2 = new Thread(task); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Counter: " + example.getCounter()); } }
Explanation
- AtomicInteger: Provides atomic operations on an
int
value. - counter.incrementAndGet(): Atomically increments by one.
Synchronizers
Synchronizers help manage the coordination between threads.
Example: Using CountDownLatch
import java.util.concurrent.CountDownLatch; public class CountDownLatchExample { public static void main(String[] args) { CountDownLatch latch = new CountDownLatch(3); Runnable task = () -> { System.out.println(Thread.currentThread().getName() + " is working"); latch.countDown(); }; for (int i = 0; i < 3; i++) { new Thread(task).start(); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("All tasks are completed"); } }
Explanation
- CountDownLatch: Allows one or more threads to wait until a set of operations being performed in other threads completes.
- latch.countDown(): Decrements the count of the latch.
- latch.await(): Causes the current thread to wait until the latch has counted down to zero.
Practical Exercise
Task
- Create a
ConcurrentHashMap
to store student names and their scores. - Use an
ExecutorService
to simulate multiple threads adding scores to the map. - Ensure thread safety and print the final map.
Solution
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ConcurrencyExercise { public static void main(String[] args) { ConcurrentHashMap<String, Integer> studentScores = new ConcurrentHashMap<>(); ExecutorService executor = Executors.newFixedThreadPool(3); Runnable task1 = () -> studentScores.put("Alice", 85); Runnable task2 = () -> studentScores.put("Bob", 90); Runnable task3 = () -> studentScores.put("Charlie", 95); executor.execute(task1); executor.execute(task2); executor.execute(task3); executor.shutdown(); while (!executor.isTerminated()) { } studentScores.forEach((name, score) -> System.out.println(name + ": " + score)); } }
Explanation
- ConcurrentHashMap: Ensures thread-safe operations.
- ExecutorService: Manages the execution of tasks.
- executor.execute(): Submits tasks for execution.
Conclusion
Concurrency utilities in Java provide powerful tools to manage multi-threaded applications efficiently. By using the Executor framework, concurrent collections, locks, atomic variables, and synchronizers, you can write robust and thread-safe code. Understanding and utilizing these utilities will significantly enhance your ability to handle concurrency in Java applications.
Java Programming Course
Module 1: Introduction to Java
- Introduction to Java
- Setting Up the Development Environment
- Basic Syntax and Structure
- Variables and Data Types
- Operators
Module 2: Control Flow
Module 3: Object-Oriented Programming
- Introduction to OOP
- Classes and Objects
- Methods
- Constructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
Module 4: Advanced Object-Oriented Programming
Module 5: Data Structures and Collections
Module 6: Exception Handling
Module 7: File I/O
Module 8: Multithreading and Concurrency
- Introduction to Multithreading
- Creating Threads
- Thread Lifecycle
- Synchronization
- Concurrency Utilities
Module 9: Networking
- Introduction to Networking
- Sockets
- ServerSocket
- DatagramSocket and DatagramPacket
- URL and HttpURLConnection