Java Multi-Threading & Concurrent Architecture
In enterprise backend applications (Spring Boot, Kafka consumers, trading systems), concurrency allows maximizing multi-core CPU throughput. This guide explores Java multi-threading principles from our Core Java & SCJP/OCJP Notes.
1. Creating Threads: `Thread` vs `Runnable` vs `Callable`
- Runnable Interface: Preferred over extending
Threadbecause Java allows single class inheritance but multiple interface implementation. - Callable<V> Interface: Used when thread execution needs to return a computed value or throw checked exceptions.
// Runnable with Java Lambda
Runnable task = () -> {
System.out.println("Executing thread: " + Thread.currentThread().getName());
};
Thread t1 = new Thread(task, "Worker-1");
t1.start();
2. Synchronization & Atomic Memory Guarantees
When multiple threads mutate shared state without synchronization, race conditions cause memory inconsistency. Use synchronized methods or Java's java.util.concurrent.atomic classes.
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadSafeCounter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // Lock-free atomic operation
}
public int getCount() {
return count.get();
}
}
3. ExecutorService Thread Pools
Spawning raw new Thread() instances on every incoming request is expensive. Use thread pools to reuse worker threads efficiently.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> System.out.println("Processing task " + taskId + " on " + Thread.currentThread().getName()));
}
executor.shutdown();