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.