Modern Concurrency Utilities in Java (java.util.concurrent)
Traditional multithreading in Java using Thread, Runnable, synchronized, and wait/notify works, but it becomes difficult to scale in modern backend applications. Java introduced the java.util.concurrent package to simplify concurrency, improve performance, and avoid common threading issues.
1. Why java.util.concurrent?
Older threading models had several limitations:
- Manual thread creation → expensive & slow
- Risk of race conditions
- Complex synchronization
- No proper thread lifecycle management
The concurrency framework solves these problems using:
- Thread pools
- Locks
- Atomic variables
- Coordination tools
- Asynchronous computation
2. ExecutorService (Thread Pooling)
ExecutorService manages threads efficiently and avoids creating too many threads.
ExecutorService service = Executors.newFixedThreadPool(5);
service.submit(() -> System.out.println("Task executed"));
Types of Executor Services:
- newSingleThreadExecutor() – only one worker thread
- newFixedThreadPool(n) – fixed-size thread pool
- newCachedThreadPool() – creates threads as needed
- newScheduledThreadPool(n) – supports scheduled tasks
3. Callable and Future (returning values)
Runnable cannot return results, but Callable can.
Callabletask = () -> 5 + 10; Future result = service.submit(task); System.out.println(result.get());
Future allows you to:
- Retrieve result using get()
- Check if task is done
- Cancel tasks
4. CompletableFuture (Modern Async Programming)
CompletableFuture enables asynchronous pipelines, non-blocking tasks, and combining results.
CompletableFuture.supplyAsync(() -> fetchData())
.thenApply(data -> transform(data))
.thenAccept(out -> save(out));
5. CountDownLatch
Used when one or more threads must wait until a set of operations complete.
CountDownLatch latch = new CountDownLatch(3); latch.await(); // waits until count reaches 0
6. CyclicBarrier
All threads wait for each other and then proceed together.
CyclicBarrier barrier = new CyclicBarrier(3); barrier.await();
7. Semaphore
Controls how many threads can access a resource at the same time.
Semaphore sem = new Semaphore(2); sem.acquire(); // critical section sem.release();
8. ReentrantLock (Advanced Locking)
More powerful than synchronized. Supports:
- tryLock()
- Fair locking
- Interruptible locking
Lock lock = new ReentrantLock();
lock.lock();
try {
// critical section
} finally {
lock.unlock();
}
9. Atomic Variables
Atomic classes provide thread-safe operations without explicit locking.
AtomicInteger count = new AtomicInteger(); count.incrementAndGet();
10. Memory Visibility & volatile
volatile ensures updated values are visible across threads but does not provide atomicity.
volatile boolean running = true;
11. Fork/Join Framework
Used for parallel computations using divide-and-conquer.
ForkJoinPool pool = new ForkJoinPool(); pool.invoke(new MyRecursiveTask());
12. Thread-safe Collections
- ConcurrentHashMap
- CopyOnWriteArrayList
- ConcurrentLinkedQueue
- BlockingQueue
13. Real-World Scenarios
- Producer-Consumer using BlockingQueue
- Reader-Writer using ReadWriteLock
- Async APIs using CompletableFuture
- Parallel processing using Fork/Join
14. Best Practices
- Always shutdown ExecutorService
- Use thread pools instead of manual threads
- Avoid shared mutable state
- Prefer immutability
- Use atomic variables for counters
- Use synchronized only when needed
15. Debugging & Profiling Threads
Recommended tools:
- Thread dumps (jstack)
- VisualVM
- IntelliJ Profiler
- JConsole
These tools help identify deadlocks, long-running threads, CPU spikes, and thread leaks.
Conclusion
Modern concurrency tools in Java make multi-threaded programming faster, cleaner, and safer. Features like ExecutorService, CompletableFuture, CountDownLatch, and atomic variables are essential for building scalable, high-performance applications.
Comments
Post a Comment