Table of Contents
- Introduction to Multithreading
- Thread Creation Methods
- Thread Lifecycle
- Thread Methods
- Thread Synchronization
- Thread Communication
- Thread Pools
- Common Patterns
- Best Practices
- Complete Examples
Introduction to Multithreading
Multithreading allows concurrent execution of multiple threads within a single program. Each thread represents an independent path of execution.
Benefits:
- Improved performance - Utilize multiple CPU cores
- Better resource utilization - Avoid blocking operations
- Enhanced responsiveness - Keep UI responsive while processing
- Simplified modeling - Natural for many real-world scenarios
Basic Concepts:
public class BasicMultithreading {
public static void main(String[] args) {
// Main thread
System.out.println("Main thread: " + Thread.currentThread().getName());
// Creating and starting threads
Thread thread1 = new MyThread("Thread-1");
Thread thread2 = new MyThread("Thread-2");
thread1.start();
thread2.start();
System.out.println("Main thread completed");
}
}
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println("Thread " + getName() + " is running");
try {
Thread.sleep(1000); // Simulate work
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread " + getName() + " completed");
}
}
Output:
Main thread: main Main thread completed Thread Thread-1 is running Thread Thread-2 is running Thread Thread-1 completed Thread Thread-2 completed
Thread Creation Methods
1. Extending Thread Class:
public class ExtendThreadExample {
public static void main(String[] args) {
System.out.println("Main thread started");
// Create threads
NumberPrinter printer1 = new NumberPrinter("Printer-1", 1, 5);
NumberPrinter printer2 = new NumberPrinter("Printer-2", 6, 10);
// Start threads
printer1.start();
printer2.start();
// Wait for threads to complete
try {
printer1.join();
printer2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread completed");
}
}
class NumberPrinter extends Thread {
private int start;
private int end;
public NumberPrinter(String name, int start, int end) {
super(name);
this.start = start;
this.end = end;
}
@Override
public void run() {
System.out.println(getName() + " started printing numbers from " + start + " to " + end);
for (int i = start; i <= end; i++) {
System.out.println(getName() + ": " + i);
try {
Thread.sleep(500); // Simulate work
} catch (InterruptedException e) {
System.out.println(getName() + " was interrupted");
return;
}
}
System.out.println(getName() + " completed");
}
}
Output:
Main thread started Printer-1 started printing numbers from 1 to 5 Printer-2 started printing numbers from 6 to 10 Printer-1: 1 Printer-2: 6 Printer-1: 2 Printer-2: 7 Printer-1: 3 Printer-2: 8 Printer-1: 4 Printer-2: 9 Printer-1: 5 Printer-2: 10 Printer-1 completed Printer-2 completed Main thread completed
2. Implementing Runnable Interface:
public class RunnableExample {
public static void main(String[] args) {
System.out.println("Main thread started");
// Create Runnable tasks
Runnable task1 = new CounterTask("Task-1", 5);
Runnable task2 = new CounterTask("Task-2", 3);
// Create threads with Runnable
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
// Set thread names
thread1.setName("Counter-Thread-1");
thread2.setName("Counter-Thread-2");
// Set thread priorities
thread1.setPriority(Thread.MIN_PRIORITY); // 1
thread2.setPriority(Thread.MAX_PRIORITY); // 10
// Start threads
thread1.start();
thread2.start();
System.out.println("Thread-1 priority: " + thread1.getPriority());
System.out.println("Thread-2 priority: " + thread2.getPriority());
System.out.println("Main thread priority: " + Thread.currentThread().getPriority());
// Wait for threads to complete
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread completed");
}
}
class CounterTask implements Runnable {
private String taskName;
private int count;
public CounterTask(String taskName, int count) {
this.taskName = taskName;
this.count = count;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " executing " + taskName);
for (int i = 1; i <= count; i++) {
System.out.println(taskName + " - Count: " + i + " (" + Thread.currentThread().getName() + ")");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(taskName + " interrupted");
return;
}
}
System.out.println(taskName + " completed");
}
}
3. Using Lambda Expressions (Java 8+):
public class LambdaThreadExample {
public static void main(String[] args) {
System.out.println("Main thread started");
// Method 1: Lambda expression
Thread thread1 = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
System.out.println("Lambda Thread - Count: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "Lambda-Thread");
// Method 2: Method reference
Thread thread2 = new Thread(LambdaThreadExample::performTask, "MethodRef-Thread");
// Method 3: Anonymous class (old way)
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println("Anonymous Thread - Count: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}, "Anonymous-Thread");
thread1.start();
thread2.start();
thread3.start();
// Wait for all threads
try {
thread1.join();
thread2.join();
thread3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread completed");
}
private static void performTask() {
for (int i = 1; i <= 3; i++) {
System.out.println("Method Reference - Count: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
Thread Lifecycle
Thread States:
public class ThreadLifecycleDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Thread Lifecycle Demonstration ===");
Thread lifecycleThread = new Thread(() -> {
System.out.println("1. Thread started - State: " + Thread.currentThread().getState());
// TIMED_WAITING state
try {
System.out.println("2. Going to sleep - Transition to TIMED_WAITING");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// RUNNABLE state
System.out.println("3. Woke up - Back to RUNNABLE");
// Some computation
long sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += i;
}
System.out.println("4. Computation completed - State: " + Thread.currentThread().getState());
}, "Lifecycle-Thread");
System.out.println("Before start - State: " + lifecycleThread.getState()); // NEW
lifecycleThread.start();
Thread.sleep(100); // Let thread start
System.out.println("After start - State: " + lifecycleThread.getState()); // RUNNABLE or TIMED_WAITING
Thread.sleep(1000); // Wait while thread is sleeping
System.out.println("During sleep - State: " + lifecycleThread.getState()); // TIMED_WAITING
lifecycleThread.join(); // Wait for thread to complete
System.out.println("After completion - State: " + lifecycleThread.getState()); // TERMINATED
// Demonstrate BLOCKED state
demonstrateBlockedState();
}
private static void demonstrateBlockedState() throws InterruptedException {
System.out.println("\n=== Demonstrating BLOCKED State ===");
Object lock = new Object();
Thread holder = new Thread(() -> {
synchronized (lock) {
System.out.println("Lock holder acquired lock");
try {
Thread.sleep(3000); // Hold lock
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Lock holder releasing lock");
}
}, "Lock-Holder");
Thread blocker = new Thread(() -> {
System.out.println("Blocker thread trying to acquire lock...");
synchronized (lock) {
System.out.println("Blocker thread acquired lock");
}
}, "Blocker");
holder.start();
Thread.sleep(100); // Ensure holder gets lock first
blocker.start();
Thread.sleep(100); // Ensure blocker tries to acquire lock
System.out.println("Blocker thread state: " + blocker.getState()); // BLOCKED
holder.join();
blocker.join();
}
}
Output:
=== Thread Lifecycle Demonstration === Before start - State: NEW After start - State: TIMED_WAITING 1. Thread started - State: RUNNABLE 2. Going to sleep - Transition to TIMED_WAITING During sleep - State: TIMED_WAITING 3. Woke up - Back to RUNNABLE 4. Computation completed - State: RUNNABLE After completion - State: TERMINATED === Demonstrating BLOCKED State === Lock holder acquired lock Blocker thread trying to acquire lock... Blocker thread state: BLOCKED Lock holder releasing lock Blocker thread acquired lock
Thread Methods
Common Thread Methods:
public class ThreadMethodsDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Thread Methods Demonstration ===");
// Thread basic methods
Thread currentThread = Thread.currentThread();
System.out.println("Current thread: " + currentThread.getName());
System.out.println("Thread ID: " + currentThread.getId());
System.out.println("Thread priority: " + currentThread.getPriority());
System.out.println("Thread state: " + currentThread.getState());
System.out.println("Thread group: " + currentThread.getThreadGroup().getName());
System.out.println("Is alive: " + currentThread.isAlive());
System.out.println("Is daemon: " + currentThread.isDaemon());
System.out.println("Is interrupted: " + currentThread.isInterrupted());
// Create and demonstrate thread methods
Thread worker = new Thread(() -> {
System.out.println("\nWorker thread started: " + Thread.currentThread().getName());
// Check for interruption
for (int i = 1; i <= 5; i++) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Worker thread interrupted, cleaning up...");
return;
}
System.out.println("Worker working... " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Worker thread interrupted during sleep");
Thread.currentThread().interrupt(); // Restore interrupt status
return;
}
}
System.out.println("Worker thread completed normally");
}, "Worker-Thread");
// Set as daemon thread
worker.setDaemon(false); // false = user thread (default)
System.out.println("\nBefore start:");
System.out.println("Is daemon: " + worker.isDaemon());
System.out.println("Is alive: " + worker.isAlive());
System.out.println("State: " + worker.getState());
worker.start();
System.out.println("\nAfter start:");
System.out.println("Is alive: " + worker.isAlive());
System.out.println("State: " + worker.getState());
// Let worker run for 2 seconds then interrupt
Thread.sleep(2000);
System.out.println("\nInterrupting worker thread...");
worker.interrupt();
// Wait for worker to finish
worker.join();
System.out.println("Worker state after join: " + worker.getState());
// Demonstrate yield()
demonstrateYield();
// Demonstrate sleep()
demonstrateSleep();
}
private static void demonstrateYield() {
System.out.println("\n=== Demonstrating yield() ===");
Thread highPriority = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
System.out.println("High priority thread working... " + i);
Thread.yield(); // Hint to scheduler to give up CPU
}
});
Thread lowPriority = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
System.out.println("Low priority thread working... " + i);
}
});
highPriority.setPriority(Thread.MAX_PRIORITY);
lowPriority.setPriority(Thread.MIN_PRIORITY);
highPriority.start();
lowPriority.start();
try {
highPriority.join();
lowPriority.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void demonstrateSleep() throws InterruptedException {
System.out.println("\n=== Demonstrating sleep() ===");
System.out.println("Starting countdown:");
for (int i = 5; i > 0; i--) {
System.out.println(i + "...");
Thread.sleep(1000); // Sleep for 1 second
}
System.out.println("Go!");
// Precise sleep with nanoseconds
long start = System.nanoTime();
Thread.sleep(1000, 500000); // Sleep for 1000.5 milliseconds
long end = System.nanoTime();
System.out.println("Actual sleep time: " + (end - start) / 1_000_000 + " ms");
}
}
Thread Synchronization
Synchronization Basics:
public class SynchronizationExample {
private static int counter = 0;
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Thread Synchronization ===");
// Without synchronization - race condition
demonstrateRaceCondition();
// Reset counter
counter = 0;
// With synchronization - thread-safe
demonstrateSynchronization();
}
private static void demonstrateRaceCondition() throws InterruptedException {
System.out.println("\n=== Without Synchronization (Race Condition) ===");
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter++; // Not thread-safe
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter++; // Not thread-safe
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Expected: 2000, Actual: " + counter + " (Race condition!)");
}
private static void demonstrateSynchronization() throws InterruptedException {
System.out.println("\n=== With Synchronization ===");
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
synchronized (lock) {
counter++; // Thread-safe
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
synchronized (lock) {
counter++; // Thread-safe
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Expected: 2000, Actual: " + counter + " (Correct!)");
// Demonstrate synchronized method
Counter sharedCounter = new Counter();
demonstrateSynchronizedMethod(sharedCounter);
}
private static void demonstrateSynchronizedMethod(Counter counter) throws InterruptedException {
System.out.println("\n=== Synchronized Methods ===");
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Counter value: " + counter.getValue() + " (Thread-safe)");
}
}
class Counter {
private int value = 0;
// Synchronized method
public synchronized void increment() {
value++;
}
// Synchronized method with condition
public synchronized void incrementWithCondition() {
// Some condition
if (value < 1000) {
value++;
}
}
public synchronized int getValue() {
return value;
}
// Synchronized block within method
public void incrementWithBlock() {
// Non-critical work can be here
synchronized (this) {
// Critical section
value++;
}
// More non-critical work can be here
}
}
Thread Communication
Wait and Notify:
public class ThreadCommunicationExample {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Thread Communication (Wait/Notify) ===");
Message message = new Message();
// Producer thread
Thread producer = new Thread(() -> {
String[] messages = {
"Hello", "How are you?", "I'm fine", "Goodbye"
};
for (String msg : messages) {
message.produce(msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
message.produce("DONE");
}, "Producer");
// Consumer thread
Thread consumer = new Thread(() -> {
String received;
do {
received = message.consume();
System.out.println("Consumer received: " + received);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} while (!"DONE".equals(received));
}, "Consumer");
producer.start();
consumer.start();
producer.join();
consumer.join();
System.out.println("Communication completed");
}
}
class Message {
private String content;
private boolean empty = true;
private final Object lock = new Object();
public void produce(String message) {
synchronized (lock) {
// Wait until message is consumed
while (!empty) {
try {
System.out.println("Producer waiting...");
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
// Produce message
content = message;
empty = false;
System.out.println("Produced: " + message);
// Notify consumer
lock.notifyAll();
}
}
public String consume() {
synchronized (lock) {
// Wait until message is available
while (empty) {
try {
System.out.println("Consumer waiting...");
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "INTERRUPTED";
}
}
// Consume message
String message = content;
empty = true;
// Notify producer
lock.notifyAll();
return message;
}
}
}
Output:
=== Thread Communication (Wait/Notify) === Produced: Hello Consumer received: Hello Consumer waiting... Produced: How are you? Consumer received: How are you? Consumer waiting... Produced: I'm fine Consumer received: I'm fine Consumer waiting... Produced: Goodbye Consumer received: Goodbye Consumer waiting... Produced: DONE Consumer received: DONE Communication completed
Thread Pools
Executor Framework:
import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) {
System.out.println("=== Thread Pools with Executor Framework ===");
// 1. Fixed Thread Pool
demonstrateFixedThreadPool();
// 2. Cached Thread Pool
demonstrateCachedThreadPool();
// 3. Scheduled Thread Pool
demonstrateScheduledThreadPool();
// 4. Single Thread Executor
demonstrateSingleThreadExecutor();
}
private static void demonstrateFixedThreadPool() {
System.out.println("\n=== Fixed Thread Pool ===");
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 6; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Task " + taskId + " completed by " + Thread.currentThread().getName());
});
}
executor.shutdown();
try {
executor.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void demonstrateCachedThreadPool() {
System.out.println("\n=== Cached Thread Pool ===");
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 1; i <= 8; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
executor.shutdown();
try {
executor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void demonstrateScheduledThreadPool() {
System.out.println("\n=== Scheduled Thread Pool ===");
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
// Schedule a task to run after 2 seconds
System.out.println("Scheduling task to run in 2 seconds...");
executor.schedule(() -> {
System.out.println("Scheduled task executed by " + Thread.currentThread().getName());
}, 2, TimeUnit.SECONDS);
// Schedule a task to run repeatedly
System.out.println("Scheduling periodic task...");
ScheduledFuture<?> future = executor.scheduleAtFixedRate(() -> {
System.out.println("Periodic task executed by " + Thread.currentThread().getName());
}, 1, 3, TimeUnit.SECONDS);
// Cancel periodic task after 10 seconds
executor.schedule(() -> {
future.cancel(false);
System.out.println("Periodic task cancelled");
}, 10, TimeUnit.SECONDS);
try {
Thread.sleep(12000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdown();
}
private static void demonstrateSingleThreadExecutor() {
System.out.println("\n=== Single Thread Executor ===");
ExecutorService executor = Executors.newSingleThreadExecutor();
for (int i = 1; i <= 5; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
executor.shutdown();
try {
executor.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Common Patterns
Producer-Consumer Pattern:
import java.util.concurrent.*;
public class ProducerConsumerPattern {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Producer-Consumer Pattern ===");
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
// Create producer and consumer
Thread producer = new Thread(new Producer(queue), "Producer");
Thread consumer = new Thread(new Consumer(queue), "Consumer");
producer.start();
consumer.start();
// Run for 10 seconds
Thread.sleep(10000);
producer.interrupt();
consumer.interrupt();
producer.join();
consumer.join();
System.out.println("Final queue size: " + queue.size());
}
}
class Producer implements Runnable {
private final BlockingQueue<Integer> queue;
private int value = 0;
public Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
int produced = ++value;
queue.put(produced); // Blocks if queue is full
System.out.println("Produced: " + produced + " (Queue size: " + queue.size() + ")");
Thread.sleep(500);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Producer stopped");
}
}
class Consumer implements Runnable {
private final BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
Integer consumed = queue.take(); // Blocks if queue is empty
System.out.println("Consumed: " + consumed + " (Queue size: " + queue.size() + ")");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Consumer stopped");
}
}
Worker Thread Pattern:
public class WorkerThreadPattern {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Worker Thread Pattern ===");
WorkerPool pool = new WorkerPool(3);
// Submit tasks
for (int i = 1; i <= 10; i++) {
final int taskId = i;
pool.submit(() -> {
System.out.println(Thread.currentThread().getName() + " executing task " + taskId);
try {
Thread.sleep(2000); // Simulate work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + " completed task " + taskId);
});
}
// Wait for tasks to complete
Thread.sleep(10000);
pool.shutdown();
}
}
class WorkerPool {
private final Worker[] workers;
private final BlockingQueue<Runnable> taskQueue;
private volatile boolean shutdown = false;
public WorkerPool(int poolSize) {
this.taskQueue = new LinkedBlockingQueue<>();
this.workers = new Worker[poolSize];
// Create and start worker threads
for (int i = 0; i < poolSize; i++) {
workers[i] = new Worker("Worker-" + (i + 1));
workers[i].start();
}
}
public void submit(Runnable task) {
if (!shutdown) {
taskQueue.offer(task);
}
}
public void shutdown() throws InterruptedException {
shutdown = true;
// Interrupt all workers
for (Worker worker : workers) {
worker.interrupt();
}
// Wait for all workers to finish
for (Worker worker : workers) {
worker.join();
}
System.out.println("Worker pool shutdown completed");
}
private class Worker extends Thread {
public Worker(String name) {
super(name);
}
@Override
public void run() {
try {
while (!shutdown || !taskQueue.isEmpty()) {
Runnable task = taskQueue.poll(1, TimeUnit.SECONDS);
if (task != null) {
task.run();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(getName() + " stopped");
}
}
}
Best Practices
1. Prefer Runnable over Thread:
public class BestPractices {
// ✅ GOOD - Implementing Runnable
static class GoodTask implements Runnable {
@Override
public void run() {
// Task logic
}
}
// ❌ LESS FLEXIBLE - Extending Thread
static class LessFlexibleTask extends Thread {
@Override
public void run() {
// Task logic
}
}
public static void main(String[] args) {
// ✅ Flexible - can use with different executors
Runnable goodTask = new GoodTask();
Thread thread1 = new Thread(goodTask);
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(goodTask);
// ❌ Limited - already a thread
LessFlexibleTask lessFlexible = new LessFlexibleTask();
lessFlexible.start(); // Only way to run
}
}
2. Proper Thread Interruption:
public class ProperInterruption {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
// ✅ GOOD - Check interruption status
while (!Thread.currentThread().isInterrupted()) {
try {
// Do work
System.out.println("Working...");
Thread.sleep(1000);
} catch (InterruptedException e) {
// ✅ GOOD - Restore interruption status and exit
System.out.println("Interrupted during sleep");
Thread.currentThread().interrupt();
break;
}
}
System.out.println("Worker thread cleaned up and exiting");
});
worker.start();
Thread.sleep(3000);
worker.interrupt();
worker.join();
}
}
3. Use Thread Pools:
public class UseThreadPools {
public static void main(String[] args) {
// ✅ GOOD - Using thread pools
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Executing task " + taskId + " on " + Thread.currentThread().getName());
});
}
executor.shutdown();
// ❌ BAD - Creating too many threads manually
for (int i = 0; i < 100; i++) {
new Thread(() -> {
// Task logic
}).start(); // Creates 100 threads - resource intensive!
}
}
}
Complete Examples
Complete Multithreading Application:
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
public class CompleteMultithreadingApp {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Complete Multithreading Application ===\n");
// Example 1: Bank Account Simulation
bankAccountSimulation();
// Example 2: Web Page Downloader
webPageDownloaderSimulation();
// Example 3: Prime Number Calculator
primeNumberCalculator();
System.out.println("\n=== All Examples Completed ===");
}
private static void bankAccountSimulation() throws InterruptedException {
System.out.println("1. Bank Account Simulation:");
BankAccount account = new BankAccount(1000);
System.out.println("Initial balance: $" + account.getBalance());
ExecutorService executor = Executors.newFixedThreadPool(3);
// Multiple concurrent transactions
List<Future<?>> futures = new ArrayList<>();
// Deposits
for (int i = 0; i < 5; i++) {
final int amount = 100;
futures.add(executor.submit(() -> {
account.deposit(amount);
System.out.println("Deposited: $" + amount + " | Balance: $" + account.getBalance());
}));
}
// Withdrawals
for (int i = 0; i < 3; i++) {
final int amount = 200;
futures.add(executor.submit(() -> {
if (account.withdraw(amount)) {
System.out.println("Withdrawn: $" + amount + " | Balance: $" + account.getBalance());
} else {
System.out.println("Failed to withdraw: $" + amount + " | Balance: $" + account.getBalance());
}
}));
}
// Wait for all transactions
for (Future<?> future : futures) {
future.get();
}
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
System.out.println("Final balance: $" + account.getBalance());
}
private static void webPageDownloaderSimulation() throws InterruptedException {
System.out.println("\n2. Web Page Downloader Simulation:");
WebPageDownloader downloader = new WebPageDownloader();
List<String> urls = Arrays.asList(
"https://example.com/page1",
"https://example.com/page2",
"https://example.com/page3",
"https://example.com/page4",
"https://example.com/page5"
);
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletionService<String> completionService = new ExecutorCompletionService<>(executor);
// Submit download tasks
for (String url : urls) {
completionService.submit(() -> downloader.downloadPage(url));
}
// Collect results as they complete
for (int i = 0; i < urls.size(); i++) {
try {
Future<String> future = completionService.take();
String result = future.get();
System.out.println("Downloaded: " + result);
} catch (ExecutionException e) {
System.out.println("Download failed: " + e.getCause().getMessage());
}
}
executor.shutdown();
}
private static void primeNumberCalculator() throws InterruptedException {
System.out.println("\n3. Prime Number Calculator:");
PrimeCalculator calculator = new PrimeCalculator();
int range = 100000;
int threadCount = 4;
long startTime = System.currentTimeMillis();
List<Integer> primes = calculator.findPrimesParallel(range, threadCount);
long endTime = System.currentTimeMillis();
System.out.println("Found " + primes.size() + " primes up to " + range);
System.out.println("First 10 primes: " + primes.subList(0, Math.min(10, primes.size())));
System.out.println("Last 10 primes: " + primes.subList(Math.max(0, primes.size() - 10), primes.size()));
System.out.println("Time taken: " + (endTime - startTime) + "ms");
}
}
// Supporting classes
class BankAccount {
private int balance;
private final Object lock = new Object();
public BankAccount(int initialBalance) {
this.balance = initialBalance;
}
public void deposit(int amount) {
synchronized (lock) {
balance += amount;
}
}
public boolean withdraw(int amount) {
synchronized (lock) {
if (balance >= amount) {
balance -= amount;
return true;
}
return false;
}
}
public int getBalance() {
synchronized (lock) {
return balance;
}
}
}
class WebPageDownloader {
private final Random random = new Random();
public String downloadPage(String url) throws InterruptedException {
// Simulate network delay
int delay = random.nextInt(3000) + 1000;
Thread.sleep(delay);
// Simulate occasional failure
if (random.nextDouble() < 0.2) {
throw new RuntimeException("Network error downloading: " + url);
}
return "Content of " + url + " (" + delay + "ms)";
}
}
class PrimeCalculator {
public List<Integer> findPrimesParallel(int max, int threadCount) throws InterruptedException {
List<Integer> allPrimes = Collections.synchronizedList(new ArrayList<>());
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
int segmentSize = max / threadCount;
List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
final int start = i * segmentSize + 1;
final int end = (i == threadCount - 1) ? max : (i + 1) * segmentSize;
futures.add(executor.submit(() -> {
List<Integer> segmentPrimes = findPrimesInRange(start, end);
allPrimes.addAll(segmentPrimes);
}));
}
// Wait for all segments to complete
for (Future<?> future : futures) {
future.get();
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
// Sort the final list
Collections.sort(allPrimes);
return allPrimes;
}
private List<Integer> findPrimesInRange(int start, int end) {
List<Integer> primes = new ArrayList<>();
for (int i = start; i <= end; i++) {
if (isPrime(i)) {
primes.add(i);
}
}
return primes;
}
private boolean isPrime(int number) {
if (number < 2) return false;
if (number == 2) return true;
if (number % 2 == 0) return false;
for (int i = 3; i * i <= number; i += 2) {
if (number % i == 0) {
return false;
}
}
return true;
}
}
Expected Output:
=== Complete Multithreading Application === 1. Bank Account Simulation: Initial balance: $1000 Deposited: $100 | Balance: $1100 Deposited: $100 | Balance: $1200 Withdrawn: $200 | Balance: $1000 Deposited: $100 | Balance: $1100 Withdrawn: $200 | Balance: $900 Deposited: $100 | Balance: $1000 Deposited: $100 | Balance: $1100 Withdrawn: $200 | Balance: $900 Final balance: $900 2. Web Page Downloader Simulation: Downloaded: Content of https://example.com/page3 (1250ms) Downloaded: Content of https://example.com/page1 (1567ms) Downloaded: Content of https://example.com/page2 (1890ms) Download failed: Network error downloading: https://example.com/page4 Downloaded: Content of https://example.com/page5 (2876ms) 3. Prime Number Calculator: Found 9592 primes up to 100000 First 10 primes: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] Last 10 primes: [99971, 99989, 99991] Time taken: 234ms === All Examples Completed ===
Key Takeaways
- Thread Creation: Extend Thread class or implement Runnable interface
- Thread Lifecycle: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
- Synchronization: Use synchronized blocks/methods for thread safety
- Communication: Use wait(), notify(), notifyAll() for thread coordination
- Thread Pools: Prefer Executor framework over manual thread management
- Best Practices: Check interruption status, use thread pools, prefer Runnable
- Common Patterns: Producer-Consumer, Worker Thread, Thread Pool patterns
Multithreading is essential for building responsive and efficient Java applications, but requires careful handling of synchronization and coordination to avoid issues like race conditions and deadlocks.