Java Multithreading Explained
Threads and Processes
- A process is an instance of a running program, representing a self-contained execution environment with its own memory space and resources.
- A thread is the smallest unit of execution scheduled by the OS. Threads exist within a process, sharing its memory and resources but having their own stack and program counter.
A process is the OS’s basic unit of resource allocation, while a thread is the basic unit of CPU scheduling. A process contains atleast one thread, and multiple threads within a process can execute concurrently, sharing data and performing different tasks.
Java programs are inherently multithreaded: launching a Java application starts at least a main thread and a garbage collection thread, with threads executing tasks in parallel.
Parallelism, Serial, and Concurrency
- Parallelism: On a multi-core CPU, multiple threads execute simultaneously (no resource contention).
- Serial: On a single-core CPU, a single thread executes tasks sequentially (no parallel execution).
- Concurrency: Multiple threads compete for a single CPU, executing in an interleaved manner (CPU switches rapidly, appearing simultaneous to users).
| Single Thread | Multi-Thread | |
|---|---|---|
| Single CPU | Serial | Concurrent |
| Multi-CPU | Serial | Parallel |
Synchronization vs. Asynchronization
- Synchronization: Tasks execute in sequence (e.g., using
synchronizedorLockto ensure ordered access to shared resources). - Asynchronization: Tasks execute independently (e.g., via callbacks,
Future, orCompletableFuture).
| Single Thread | Multi-Thread | |
|---|---|---|
| Sync | Sequential execution | Coordinated execution |
| Async | Simulated via event loops | Parallel execution via thread pools |
Daemon Threads
Daemon threads (e.g., garbage collection) serve other threads and terminate when no user threads remain. Example:
public class DaemonDemo {
public static void main(String[] args) {
int i = 0;
while (true) {
Thread daemon = new Thread(() -> {
System.out.println("Thread started: " + Thread.currentThread().getName());
});
daemon.setDaemon(i == 3);
daemon.start();
boolean isDaemon = daemon.isDaemon();
System.out.println("Is daemon? " + isDaemon);
if (isDaemon) break;
i++;
}
}
}
Thread Safety
Thread safety ensures shared resources are accessed/modified without data corruption. Issues arise from:
- Atomicity: Interrupted operations (e.g.,
count++is non-atomic). - Visibility: Stale cache values (e.g., non-volatile variables).
- Ordering: Reordered instructions (e.g., JVM/CPU optimizations).
Thread States
Java threads have 6 states (via Thread.State):
- NEW: Created but not started.
- RUNNABLE: Executing or ready to execute.
- BLOCKED: Waiting for a monitor lock.
- WAITING: Waiting indefinitely (e.g.,
Object.wait(),Thread.join()). - TIMED_WAITING: Waiting with a timeout (e.g.,
Thread.sleep()). - TERMINATED: Completed execution.
Thread Methods
Key methods:
start(): Launches a thread (callsrun()).run(): Defines thread logic (override or pass viaRunnable).sleep(): Pauses execution (no lock release).join(): Waits for a thread to finish.wait()/notify(): Coordinates thread execution (requires synchronization).
Creating Threads
Thread Class
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running via Thread class");
}
}
// Usage:
MyThread t = new MyThread();
t.start();
Runnable Interface
Runnable task = () -> System.out.println("Thread via Runnable");
new Thread(task).start();
Callable and Future
FutureTask<String> future = new FutureTask<>(() -> {
Thread.sleep(1000);
return "Callable result";
});
new Thread(future).start();
String result = future.get(); // Blocks until complete
Thread Pool
ExecutorService pool = new ThreadPoolExecutor(
2, 5, 1, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
pool.execute(() -> System.out.println("Task in pool"));
pool.shutdown();
Locks
ReentrantLock (Fair/Non-Fair)
ReentrantLock fairLock = new ReentrantLock(true);
ReentrantLock nonFairLock = new ReentrantLock(false);
ReadWriteLock
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();
CAS (Compare-And-Swap)
AtomicInteger atomic = new AtomicInteger(0);
atomic.compareAndSet(0, 1); // Atomically updates if current value is 0
Java Memory Model (JMM)
JMM ensures atomicity, visibility, and ordering:
- Atomicity: Operations like
synchronizedblocks are uninterruptible. - Visibility:
volatilevariables are immediately visible to other threads. - Ordering:
volatileandsynchronizedprevent instruction reordering.
volatile Keyword
volatile boolean flag = false;
// Ensures visibility and ordering
synchronized Keyword
synchronized void syncMethod() {
// Thread-safe block
}
J.U.C. Package
AQS (AbstractQueuedSynchronizer)
Used by ReentrantLock, CountDownLatch, etc., to manage thread queues.
CountDownLatch
CountDownLatch latch = new CountDownLatch(3);
// Threads call latch.countDown();
latch.await(); // Waits for count to reach 0
CyclicBarrier
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("All threads ready"));
// Threads call barrier.await();
Semaphore
Semaphore sem = new Semaphore(2); // Allows 2 concurrent threads
sem.acquire(); // Blocks if no permits
sem.release(); // Releases a permit
ThreadLocal
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Default");
threadLocal.set("Thread-specific");
String value = threadLocal.get();
threadLocal.remove(); // Prevent memory leaks
Thread-Safe Collections
CopyOnWriteArrayList
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("Thread-safe");
ConcurrentHashMap
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);