| name | java-concurrency |
| user-invocable | false |
| description | Use when Java concurrency with ExecutorService, CompletableFuture, and virtual threads. Use when building concurrent applications. |
| allowed-tools | ["Bash","Read","Write","Edit"] |
Java Concurrency
Master Java's concurrency utilities including ExecutorService,
CompletableFuture, locks, and modern virtual threads for building
high-performance concurrent applications.
Thread Basics
Understanding Java threads is fundamental to concurrency.
Creating and running threads:
public class ThreadBasics {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println("Running in thread: " +
Thread.currentThread().getName());
});
thread1.start();
Runnable task = () -> {
for (int i = 0; i < 5; i++) {
System.out.println("Task iteration: " + i);
}
};
Thread thread2 = new Thread(task);
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
ExecutorService
ExecutorService provides thread pool management and task scheduling.
Basic executor usage:
import java.util.concurrent.*;
public class ExecutorBasics {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " on " +
Thread.currentThread().getName());
return taskId * 2;
});
}
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
Different executor types:
public class ExecutorTypes {
public static void main(String[] args) {
ExecutorService single = Executors.newSingleThreadExecutor();
ExecutorService fixed = Executors.newFixedThreadPool(4);
ExecutorService cached = Executors.newCachedThreadPool();
ScheduledExecutorService scheduled =
Executors.newScheduledThreadPool(2);
scheduled.schedule(() -> {
System.out.println("Delayed task");
}, 5, TimeUnit.SECONDS);
scheduled.scheduleAtFixedRate(() -> {
System.out.println("Periodic task");
}, 0, 1, TimeUnit.SECONDS);
ExecutorService workStealing =
Executors.newWorkStealingPool();
}
}
Future pattern:
public class FutureExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Integer> future = executor.submit(() -> {
Thread.sleep(1000);
return 42;
});
System.out.println("Waiting for result...");
Integer result = future.get();
System.out.println("Result: " + result);
try {
Integer result2 = future.get(500, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
System.out.println("Timed out");
future.cancel(true);
}
boolean isDone = future.isDone();
boolean isCancelled = future.isCancelled();
executor.shutdown();
}
}
CompletableFuture
CompletableFuture enables composable asynchronous programming.
Basic CompletableFuture:
import java.util.concurrent.CompletableFuture;
public class CompletableFutureBasics {
public static void main(String[] args) {
CompletableFuture<String> future =
CompletableFuture.completedFuture("Hello");
CompletableFuture<Integer> asyncFuture =
CompletableFuture.supplyAsync(() -> {
sleep(1000);
return 42;
});
CompletableFuture<Void> runAsync =
CompletableFuture.runAsync(() -> {
System.out.println("Running async");
});
try {
Integer result = asyncFuture.get();
System.out.println("Result: " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
Chaining operations:
public class CompletableFutureChaining {
public static void main(String[] args) {
CompletableFuture<String> future =
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenApply(String::toUpperCase);
System.out.println(future.join());
CompletableFuture.supplyAsync(() -> 42)
.thenAccept(result ->
System.out.println("Result: " + result));
CompletableFuture.supplyAsync(() -> "Done")
.thenRun(() -> System.out.println("Finished"));
CompletableFuture<String> composed =
CompletableFuture.supplyAsync(() -> "User123")
.thenCompose(userId -> fetchUserDetails(userId));
}
static CompletableFuture<String> fetchUserDetails(String userId) {
return CompletableFuture.supplyAsync(() ->
"Details for " + userId);
}
}
Combining futures:
public class CombiningFutures {
public static void main(String[] args) {
CompletableFuture<Integer> future1 =
CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> future2 =
CompletableFuture.supplyAsync(() -> 20);
CompletableFuture<Integer> combined = future1.thenCombine(
future2,
(a, b) -> a + b
);
System.out.println(combined.join());
future1.thenAcceptBoth(future2, (a, b) ->
System.out.println("Sum: " + (a + b)));
future1.runAfterBoth(future2, () ->
System.out.println("Both completed"));
CompletableFuture<String> either =
future1.applyToEither(future2, result ->
"First result: " + result);
CompletableFuture<Void> allOf =
CompletableFuture.allOf(future1, future2);
CompletableFuture<Object> anyOf =
CompletableFuture.anyOf(future1, future2);
}
}
Error handling:
public class FutureErrorHandling {
public static void main(String[] args) {
CompletableFuture<Integer> future1 =
CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) {
throw new RuntimeException("Error!");
}
return 42;
}).exceptionally(ex -> {
System.err.println("Error: " + ex.getMessage());
return -1;
});
CompletableFuture<Integer> future2 =
CompletableFuture.supplyAsync(() -> 10 / 0)
.handle((result, ex) -> {
if (ex != null) {
System.err.println("Error: " + ex.getMessage());
return 0;
}
return result;
});
CompletableFuture.supplyAsync(() -> "Hello")
.whenComplete((result, ex) -> {
if (ex != null) {
System.err.println("Failed");
} else {
System.out.println("Success: " + result);
}
});
}
}
Locks and Synchronization
Beyond synchronized blocks, Java provides explicit locks.
ReentrantLock:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
private int counter = 0;
public void increment() {
lock.lock();
try {
counter++;
} finally {
lock.unlock();
}
}
public void tryLockExample() {
if (lock.tryLock()) {
try {
counter++;
} finally {
lock.unlock();
}
} else {
System.out.println("Could not acquire lock");
}
}
public void fairLockExample() {
ReentrantLock fairLock = new ReentrantLock(true);
fairLock.lock();
try {
} finally {
fairLock.unlock();
}
}
}
ReadWriteLock:
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.HashMap;
import java.util.Map;
public class ReadWriteLockExample {
private final Map<String, String> cache = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public String get(String key) {
lock.readLock().lock();
try {
return cache.get(key);
} finally {
lock.readLock().unlock();
}
}
public void put(String key, String value) {
lock.writeLock().lock();
try {
cache.put(key, value);
} finally {
lock.writeLock().unlock();
}
}
}
Condition variables:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.LinkedList;
import java.util.Queue;
public class BoundedBuffer<T> {
private final Queue<T> queue = new LinkedList<>();
private final int capacity;
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public BoundedBuffer(int capacity) {
this.capacity = capacity;
}
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await();
}
queue.add(item);
notEmpty.signal();
} finally {
lock.unlock();
}
}
T InterruptedException {
lock.lock();
{
(queue.isEmpty()) {
notEmpty.await();
}
queue.remove();
notFull.signal();
item;
} {
lock.unlock();
}
}
}
CountDownLatch and CyclicBarrier
Coordination utilities for managing thread synchronization.
CountDownLatch:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int workerCount = 3;
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(workerCount);
for (int i = 0; i < workerCount; i++) {
new Thread(new Worker(startSignal, doneSignal)).start();
}
System.out.println("Preparing workers...");
Thread.sleep(1000);
startSignal.countDown();
doneSignal.await();
System.out.println("All workers completed");
}
static class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
.startSignal = startSignal;
.doneSignal = doneSignal;
}
{
{
startSignal.await();
doWork();
doneSignal.countDown();
} (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
{
System.out.println( +
Thread.currentThread().getName() + );
}
}
}
CyclicBarrier:
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.BrokenBarrierException;
public class CyclicBarrierExample {
public static void main(String[] args) {
int parties = 3;
CyclicBarrier barrier = new CyclicBarrier(parties, () -> {
System.out.println("All threads reached barrier");
});
for (int i = 0; i < parties; i++) {
new Thread(new Task(barrier, i)).start();
}
}
static class Task implements Runnable {
private final CyclicBarrier barrier;
private final int id;
Task(CyclicBarrier barrier, int id) {
this.barrier = barrier;
this.id = id;
}
public void run() {
try {
System.out.println("Task " + id + " working");
Thread.sleep( * id);
System.out.println( + id + );
barrier.await();
System.out.println( + id + );
} (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
}
}
}
Virtual Threads
Virtual threads enable lightweight concurrency at scale.
Creating virtual threads:
public class VirtualThreads {
public static void main(String[] args) throws InterruptedException {
Thread vThread = Thread.startVirtualThread(() -> {
System.out.println("Running in virtual thread: " +
Thread.currentThread());
});
vThread.join();
Thread virtual = Thread.ofVirtual()
.name("virtual-worker")
.start(() -> {
System.out.println("Virtual thread task");
});
virtual.join();
ThreadFactory factory = Thread.ofVirtual().factory();
Thread t = factory.newThread(() -> {
System.out.println("Created by factory");
});
t.start();
t.join();
}
}
Virtual thread executor:
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
public class VirtualThreadExecutor {
public static void main(String[] args) {
try (ExecutorService executor =
Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
final int taskId = i;
executor.submit(() -> {
Thread.sleep(1000);
System.out.println("Task " + taskId);
return null;
});
}
}
}
}
Atomic Variables
Lock-free thread-safe operations using atomic classes.
AtomicInteger and friends:
import java.util.concurrent.atomic.*;
public class AtomicExample {
private final AtomicInteger counter = new AtomicInteger(0);
private final AtomicLong longCounter = new AtomicLong(0);
private final AtomicBoolean flag = new AtomicBoolean(false);
private final AtomicReference<String> ref =
new AtomicReference<>("initial");
public void increment() {
counter.incrementAndGet();
}
public int getAndAdd(int delta) {
return counter.getAndAdd(delta);
}
public boolean compareAndSet(int expect, int update) {
return counter.compareAndSet(expect, update);
}
public void {
ref.updateAndGet(current -> current.toUpperCase());
}
{
counter.accumulateAndGet(, (current, value) ->
current + value * );
}
}
Thread-Safe Collections
Concurrent collections for safe multi-threaded access.
ConcurrentHashMap:
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentMapExample {
private final ConcurrentHashMap<String, Integer> map =
new ConcurrentHashMap<>();
public void basicOperations() {
map.put("key", 1);
map.putIfAbsent("key", 2);
map.compute("key", (k, v) -> v == null ? 1 : v + 1);
map.computeIfAbsent("newKey", k -> k.length());
map.computeIfPresent("key", (k, v) -> v * 2);
map.replace("key", 1, 10);
map.merge("key", 5, (oldVal, newVal) -> oldVal + newVal);
}
public void bulkOperations() {
map.forEach(10, (k, v) ->
System.out.println(k + " = " + v));
map.search(, (k, v) ->
v > ? k : );
map.reduce(,
(k, v) -> v,
(v1, v2) -> v1 + v2);
}
}
Other concurrent collections:
import java.util.concurrent.*;
public class ConcurrentCollections {
public static void main(String[] args) {
BlockingQueue<String> queue =
new LinkedBlockingQueue<>(10);
BlockingQueue<Integer> priorityQueue =
new PriorityBlockingQueue<>();
ConcurrentLinkedQueue<String> linkedQueue =
new ConcurrentLinkedQueue<>();
CopyOnWriteArrayList<String> list =
new CopyOnWriteArrayList<>();
ConcurrentSkipListMap<String, Integer> skipList =
new ConcurrentSkipListMap<>();
}
}
When to Use This Skill
Use java-concurrency when you need to:
- Execute tasks concurrently with thread pools
- Perform asynchronous operations with callbacks
- Coordinate multiple threads with barriers or latches
- Implement producer-consumer patterns
- Handle high-concurrency scenarios with virtual threads
- Protect shared state with locks or atomic operations
- Process tasks in parallel for better performance
- Implement timeout and cancellation for long operations
- Build reactive or event-driven applications
- Scale applications to handle thousands of concurrent tasks
Best Practices
- Use ExecutorService instead of raw threads
- Always shutdown executors properly
- Prefer CompletableFuture for async operations
- Use virtual threads for I/O-bound tasks
- Minimize lock contention and critical sections
- Use concurrent collections over synchronized collections
- Handle InterruptedException appropriately
- Avoid blocking operations in CompletableFuture chains
- Use try-finally for lock acquisition/release
- Consider using atomic variables over locks
Common Pitfalls
- Not shutting down executors (resource leak)
- Blocking virtual threads with synchronized blocks
- Deadlocks from incorrect lock ordering
- Race conditions from improper synchronization
- Thread pool exhaustion from blocking tasks
- Ignoring InterruptedException
- Using too many platform threads (use virtual threads)
- Not handling CompletableFuture exceptions
- Excessive lock contention hurting performance
- Incorrect use of volatile vs atomic vs synchronized
Resources