Skip to main content

๐Ÿงต 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โ€‹

  1. 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
  2. Thread Creation Methods

    • Extending Thread class
    • Implementing Runnable interface
    • Using ExecutorService
    • Anonymous classes
    • Lambda expressions

Implementation Examples ๐Ÿ’ปโ€‹

Basic Thread Creationโ€‹

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();
}
}

Thread Synchronizationโ€‹

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();
}
}
}

Best Practices ๐ŸŒŸโ€‹

Thread Creation and Managementโ€‹

  1. Use Thread Pools
// Good: Using thread pool
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(task);

// Bad: Creating new threads manually
new Thread(task).start();
  1. 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();
  1. Exception Handling
// Good: Proper exception handling
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
logger.error("Thread " + thread.getName() + " failed", throwable);
});

Thread Safety Guidelinesโ€‹

  1. Immutable Objects
public final class ImmutableValue {
private final int value;

public ImmutableValue(int value) {
this.value = value;
}

public int getValue() {
return value;
}
}
  1. Atomic Operations
private final AtomicInteger counter = new AtomicInteger(0);

public void increment() {
counter.incrementAndGet();
}

Common Pitfalls ๐Ÿšจโ€‹

  1. 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
}
}
}
  1. 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โ€‹

  1. Priority and Time Slicing
Thread thread = new Thread(task);
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
  1. 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:

  1. Use consistent lock ordering
  2. Avoid nested locks
  3. Use lock timeouts
  4. Use Lock.tryLock() when appropriate
  5. 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:

  1. Using fair locks
  2. Avoiding long-held locks
  3. Using appropriate thread priorities
  4. Implementing timeouts