๐งต Java Core Concurrency: Threads
Overview ๐ฏโ
Java Threads are the fundamental unit of program execution in concurrent programming. A thread represents an independent path of execution within a program, allowing multiple tasks to run concurrently.
Real-World Analogyโ
Think of threads like workers in a restaurant:
- Main Thread: The head chef coordinating everything
- Worker Threads: Line cooks handling different dishes simultaneously
- Thread Synchronization: Kitchen stations that can only be used by one cook at a time
- Thread Pool: A team of servers ready to take orders to tables
Key Concepts ๐โ
Core Componentsโ
-
Thread States
- NEW: Created but not yet started
- RUNNABLE: Executing or ready to execute
- BLOCKED: Waiting for monitor lock
- WAITING: Waiting indefinitely
- TIMED_WAITING: Waiting for specified time
- TERMINATED: Completed execution
-
Thread Creation Methods
- Extending Thread class
- Implementing Runnable interface
- Using ExecutorService
- Anonymous classes
- Lambda expressions
Implementation Examples ๐ปโ
Basic Thread Creationโ
- Java
- Go
import java.util.concurrent.*;
public class ThreadExamples {
// Method 1: Extending Thread
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running: " + Thread.currentThread().getName());
}
}
// Method 2: Implementing Runnable
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running: " + Thread.currentThread().getName());
}
}
public void demonstrateThreads() {
// Using Thread class
Thread thread1 = new MyThread();
thread1.start();
// Using Runnable
Thread thread2 = new Thread(new MyRunnable());
thread2.start();
// Using Lambda
Thread thread3 = new Thread(() ->
System.out.println("Lambda thread: " + Thread.currentThread().getName())
);
thread3.start();
// Using ExecutorService
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() ->
System.out.println("Executor thread: " + Thread.currentThread().getName())
);
executor.shutdown();
}
}
package main
import (
"fmt"
"sync"
)
func main() {
// Using goroutines (Go's lightweight threads)
var wg sync.WaitGroup
// Method 1: Simple goroutine
wg.Add(1)
go func() {
defer wg.Done()
fmt.Printf("Goroutine running\n")
}()
// Method 2: Named function
wg.Add(1)
go runTask(&wg)
// Wait for all goroutines to complete
wg.Wait()
}
func runTask(wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Named function running\n")
}
Thread Synchronizationโ
- Java
- Go
public class SynchronizationExample {
private int count = 0;
private final Object lock = new Object();
// Method level synchronization
public synchronized void incrementSync() {
count++;
}
// Block level synchronization
public void incrementBlock() {
synchronized(lock) {
count++;
}
}
// Using ReentrantLock
private final ReentrantLock reentrantLock = new ReentrantLock();
public void incrementWithLock() {
reentrantLock.lock();
try {
count++;
} finally {
reentrantLock.unlock();
}
}
// Using ReadWriteLock
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void writeWithLock() {
rwLock.writeLock().lock();
try {
count++;
} finally {
rwLock.writeLock().unlock();
}
}
public int readWithLock() {
rwLock.readLock().lock();
try {
return count;
} finally {
rwLock.readLock().unlock();
}
}
}
package main
import (
"sync"
)
type SynchronizationExample struct {
count int
mutex sync.Mutex
rwMutex sync.RWMutex
}
// Using Mutex
func (s *SynchronizationExample) incrementSync() {
s.mutex.Lock()
defer s.mutex.Unlock()
s.count++
}
// Using RWMutex for read/write operations
func (s *SynchronizationExample) writeWithLock() {
s.rwMutex.Lock()
defer s.rwMutex.Unlock()
s.count++
}
func (s *SynchronizationExample) readWithLock() int {
s.rwMutex.RLock()
defer s.rwMutex.RUnlock()
return s.count
}
Best Practices ๐โ
Thread Creation and Managementโ
- Use Thread Pools
// Good: Using thread pool
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(task);
// Bad: Creating new threads manually
new Thread(task).start();
- Thread Naming
// Good: Named threads for debugging
Thread thread = new Thread(task);
thread.setName("Worker-" + threadId);
thread.start();
// Bad: Default thread names
new Thread(task).start();
- Exception Handling
// Good: Proper exception handling
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
logger.error("Thread " + thread.getName() + " failed", throwable);
});
Thread Safety Guidelinesโ
- Immutable Objects
public final class ImmutableValue {
private final int value;
public ImmutableValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
- Atomic Operations
private final AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet();
}
Common Pitfalls ๐จโ
- Deadlocks
// Wrong: Potential deadlock
public void method1() {
synchronized(lockA) {
synchronized(lockB) {
// do something
}
}
}
public void method2() {
synchronized(lockB) { // Reverse order of locks
synchronized(lockA) {
// do something
}
}
}
// Correct: Consistent lock ordering
public void method1() {
synchronized(lockA) {
synchronized(lockB) {
// do something
}
}
}
public void method2() {
synchronized(lockA) { // Same order as method1
synchronized(lockB) {
// do something
}
}
}
- Thread Leaks
// Wrong: Unclosed executor
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(task);
// ... program ends without shutdown
// Correct: Proper shutdown
ExecutorService executor = Executors.newFixedThreadPool(10);
try {
executor.submit(task);
} finally {
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
}
Use Cases ๐ฏโ
1. Background Task Processorโ
public class BackgroundTaskProcessor {
private final ExecutorService executor;
public BackgroundTaskProcessor(int threadCount) {
this.executor = Executors.newFixedThreadPool(threadCount);
}
public Future<Result> processTask(Task task) {
return executor.submit(() -> {
// Process task
return new Result();
});
}
public void shutdown() {
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
2. Web Server Request Handlerโ
public class RequestHandler {
private final ThreadPoolExecutor executor;
public RequestHandler(int corePoolSize, int maxPoolSize) {
this.executor = new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100)
);
}
public void handleRequest(Request request) {
executor.execute(() -> {
try {
processRequest(request);
} catch (Exception e) {
logger.error("Error processing request", e);
}
});
}
private void processRequest(Request request) {
// Process the request
}
}
3. Parallel Data Processorโ
public class ParallelDataProcessor {
private final ForkJoinPool forkJoinPool;
public ParallelDataProcessor() {
this.forkJoinPool = new ForkJoinPool();
}
public <T> List<T> processData(List<T> data, Function<T, T> processor) {
return data.parallelStream()
.map(processor)
.collect(Collectors.toList());
}
}
Deep Dive Topics ๐โ
Thread Schedulingโ
- Priority and Time Slicing
Thread thread = new Thread(task);
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
- Yielding
public void cooperativeMultitasking() {
while (processing) {
// Do some work
if (shouldYield) {
Thread.yield();
}
}
}
Thread Local Storageโ
public class ThreadLocalExample {
private static final ThreadLocal<UserContext> userContext =
new ThreadLocal<>();
public void setContext(UserContext context) {
userContext.set(context);
}
public UserContext getContext() {
return userContext.get();
}
public void clearContext() {
userContext.remove();
}
}
Additional Resources ๐โ
Official Documentationโ
Toolsโ
FAQs โโ
Q: When should I use Thread vs ExecutorService?โ
A: Use ExecutorService for most cases as it provides better thread management, reuse, and control. Use raw threads only for simple, one-off tasks.
Q: How do I handle InterruptedException?โ
A: Either propagate it up the call stack or restore the interrupt status:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// Handle interruption
}
Q: What's the difference between synchronized and Lock?โ
A: Lock provides more flexibility with features like tryLock(), timed lock attempts, and interruptible lock attempts. synchronized is simpler but less flexible.
Q: How do I prevent deadlocks?โ
A: Follow these guidelines:
- Use consistent lock ordering
- Avoid nested locks
- Use lock timeouts
- Use Lock.tryLock() when appropriate
- Implement deadlock detection
Q: What is thread starvation and how to prevent it?โ
A: Thread starvation occurs when threads can't access shared resources. Prevent it by:
- Using fair locks
- Avoiding long-held locks
- Using appropriate thread priorities
- Implementing timeouts