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.