Skip to main content

10.4 Concurrency Utilities

Overview​

Java's Concurrency Utilities, introduced in the java.util.concurrent package, provide a comprehensive set of classes and interfaces to manage complex concurrent operations more easily and efficiently. These utilities help manage threads, coordinate tasks, handle thread-safe collections, and streamline parallel programming in Java.


1. Executors​

The Executors framework simplifies the creation and management of thread pools, providing an efficient way to manage multiple threads in complex applications.

Example: Fixed Thread Pool​

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorsExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3); // Pool with 3 threads

for (int i = 1; i <= 5; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " is running by " + Thread.currentThread().getName());
});
}

executor.shutdown(); // Shut down the executor
}
}

In this example, a fixed thread pool is created with 3 threads, allowing multiple tasks to be executed concurrently without exceeding the limit.


2. Callable and Future​

The Callable interface is similar to Runnable but can return a result and throw checked exceptions. The Future interface is used to retrieve the result of a Callable after it completes.

Example: Callable and Future​

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableFutureExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();

Callable<Integer> task = () -> {
Thread.sleep(1000);
return 123;
};

Future<Integer> future = executor.submit(task);

try {
System.out.println("Result from callable: " + future.get()); // Output: 123
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}

executor.shutdown();
}
}

In this example, Callable is used to return a result (123), which can be retrieved using Future.get().


3. CountdownLatch​

The CountDownLatch is used to delay the execution of a thread until other threads have completed specific tasks.

Example: CountdownLatch​

import java.util.concurrent.CountDownLatch;

public class CountdownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3); // Latch with count 3

for (int i = 1; i <= 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " completed.");
latch.countDown(); // Reduce the latch count
}).start();
}

latch.await(); // Main thread waits until count reaches 0
System.out.println("All threads completed. Proceeding with main thread.");
}
}

In this example, the main thread waits until all other threads finish their work, indicated by the latch count reaching zero.


4. CyclicBarrier​

The CyclicBarrier allows multiple threads to wait for each other at a common barrier point, which can be reused after all threads reach the barrier.

Example: CyclicBarrier​

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("All parties reached the barrier"));

for (int i = 1; i <= 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " reached barrier.");
try {
barrier.await(); // Wait until all threads reach this point
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}

In this example, all threads must reach the barrier before proceeding, triggering the barrier’s action.


5. Concurrent Collections​

Java provides thread-safe collections that allow concurrent access, helping prevent data inconsistency without external synchronization.

Example: Using ConcurrentHashMap​

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentCollectionExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

map.put("A", 1);
map.put("B", 2);

map.forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value));
}
}

In this example, ConcurrentHashMap allows thread-safe access to the map, preventing concurrency issues.


Summary​

  • Executors simplify thread management and pooling.
  • Callable and Future enable returning results and handling exceptions in concurrent tasks.
  • CountDownLatch and CyclicBarrier coordinate tasks by managing dependencies.
  • Concurrent Collections provide thread-safe data structures for concurrent programming.

Java’s Concurrency Utilities make multithreading more manageable and robust, helping developers create efficient, high-performance applications.