Concurrency Utilities in Java
The java.util.concurrent
package provides powerful utilities that simplify building thread-safe and scalable applications. Instead of manually handling synchronization, locks, and wait/notify, developers can use these high-level constructs to coordinate threads and manage shared resources effectively.
1. CountDownLatch
A CountDownLatch allows one or more threads to wait until a set of operations being performed by other threads completes.
- Use case: Ensuring that a service starts only after multiple worker threads finish initialization.
- Behavior: Initialized with a count; each
countDown()
call decreases it. Threads callingawait()
block until the count reaches zero.
import java.util.concurrent.CountDownLatch;
class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
Runnable worker = () -> {
try {
Thread.sleep(1000); // simulate work
System.out.println(Thread.currentThread().getName() + " finished work");
latch.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
for (int i = 0; i < 3; i++) {
new Thread(worker).start();
}
latch.await(); // wait for all workers
System.out.println("All workers finished. Proceeding...");
}
}
✅ Pros: Simple one-time synchronization.
⚠️ Cons: Latch cannot be reset; for reusable synchronization, use CyclicBarrier
.
2. CyclicBarrier
A CyclicBarrier makes threads wait until a predefined number of threads reach the barrier, then releases all at once.
- Use case: Coordinating phases in parallel computations (e.g., divide-and-conquer algorithms).
- Behavior: Can be reused (unlike
CountDownLatch
). Optional barrier action runs once when all parties arrive.
import java.util.concurrent.CyclicBarrier;
class CyclicBarrierExample {
public static void main(String[] args) {
Runnable barrierAction = () -> System.out.println("All parties arrived, barrier lifted!");
CyclicBarrier barrier = new CyclicBarrier(3, barrierAction);
Runnable task = () -> {
try {
System.out.println(Thread.currentThread().getName() + " waiting...");
barrier.await();
System.out.println(Thread.currentThread().getName() + " passed barrier");
} catch (Exception e) {
e.printStackTrace();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
}
✅ Pros: Reusable, supports barrier action.
⚠️ Cons: More complex error handling (BrokenBarrierException
).
3. Semaphore
A Semaphore controls access to a limited number of resources by maintaining permits.
- Use case: Limiting concurrent access to a shared resource (e.g., 3 threads allowed in a database connection pool).
- Behavior: Threads call
acquire()
to get a permit andrelease()
to return it.
import java.util.concurrent.Semaphore;
class SemaphoreExample {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); // 3 permits
Runnable worker = () -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " acquired permit");
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
System.out.println(Thread.currentThread().getName() + " released permit");
semaphore.release();
}
};
for (int i = 0; i < 6; i++) {
new Thread(worker).start();
}
}
}
✅ Pros: Ideal for rate limiting or resource pools.
⚠️ Cons: Mismanagement (forgetting release()
) can cause deadlocks.
4. BlockingQueue
A BlockingQueue provides thread-safe producer-consumer support, blocking on put()
if full and take()
if empty.
- Use case: Producer-consumer systems, job queues.
- Types:
ArrayBlockingQueue
: bounded, fixed capacity.LinkedBlockingQueue
: optionally bounded, scalable.PriorityBlockingQueue
: orders elements by priority.DelayQueue
: elements available only after delay.
import java.util.concurrent.*;
class BlockingQueueExample {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
Runnable producer = () -> {
try {
for (int i = 0; i < 10; i++) {
queue.put(i);
System.out.println("Produced: " + i);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
Runnable consumer = () -> {
try {
for (int i = 0; i < 10; i++) {
int item = queue.take();
System.out.println("Consumed: " + item);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
new Thread(producer).start();
new Thread(consumer).start();
}
}
✅ Pros: Simplifies producer-consumer patterns.
⚠️ Cons: Need tuning of queue size; risk of blocking under pressure.
5. ConcurrentHashMap
A ConcurrentHashMap is a thread-safe alternative to HashMap
without global locking.
- Use case: Shared mutable maps with high read/write concurrency.
- Behavior: Uses fine-grained locks (segments/buckets in older versions, synchronized nodes in JDK 8+).
import java.util.concurrent.*;
class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("A", 1);
map.putIfAbsent("B", 2);
map.compute("A", (k, v) -> v + 1);
System.out.println("Value of A: " + map.get("A"));
}
}
✅ Pros: Highly concurrent, non-blocking reads.
⚠️ Cons: Iterators are weakly consistent (may not reflect latest changes).
6. Thread-Safe Collections
CopyOnWriteArrayList
: Optimized for frequent reads and rare writes (writes copy entire array).CopyOnWriteArraySet
: Backed byCopyOnWriteArrayList
.ConcurrentLinkedQueue
: Non-blocking, thread-safe queue based on CAS (compare-and-swap).
import java.util.concurrent.CopyOnWriteArrayList;
class CopyOnWriteExample {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
for (String s : list) {
System.out.println(s);
list.add("C"); // safe, no ConcurrentModificationException
}
}
}
✅ Pros: Safe iteration during concurrent modifications.
⚠️ Cons: Expensive writes; not suitable for write-heavy scenarios.
Summary
The Java Concurrency Utilities simplify multi-threaded programming with higher-level constructs:
CountDownLatch
andCyclicBarrier
for coordination.Semaphore
for resource control.BlockingQueue
for producer-consumer systems.ConcurrentHashMap
and thread-safe collections for shared data.
👉 Mastering these utilities is critical for building scalable systems and answering concurrency questions in FAANG interviews.