google-site-verification: google61fe8ba583a51912.html
Multithreading Basics in Java

1. Introduction to Multithreading

What is Multithreading?

Multithreading is a programming concept that allows multiple threads to execute concurrently within a single program. Each thread represents an independent path of execution.

Why Use Multithreading?

  • Better CPU utilization
  • Improved performance for I/O bound tasks
  • Responsive applications (UI doesn't freeze)
  • Parallel processing capabilities
  • Better resource utilization

Key Concepts

  • Thread: Lightweight sub-process, smallest unit of execution
  • Process: Independent program with its own memory space
  • Concurrency: Multiple tasks making progress simultaneously
  • Parallelism: Multiple tasks executing at exactly the same time

2. Thread Lifecycle

public class ThreadLifecycle { public static void main(String[] args) throws InterruptedException { System.out.println("=== Thread Lifecycle Demo ==="); Thread thread = new Thread(() -> { try { System.out.println("Thread is RUNNING"); Thread.sleep(1000); // TIMED_WAITING System.out.println("Thread completed execution"); } catch (InterruptedException e) { System.out.println("Thread was INTERRUPTED"); } }); System.out.println("Thread state: " + thread.getState()); // NEW thread.start(); System.out.println("Thread state after start: " + thread.getState()); // RUNNABLE Thread.sleep(100); System.out.println("Thread state during sleep: " + thread.getState()); // TIMED_WAITING thread.join(); // Wait for thread to complete System.out.println("Thread state after completion: " + thread.getState()); // TERMINATED } }

Thread States:

  1. NEW - Created but not started
  2. RUNNABLE - Ready to run or running
  3. BLOCKED - Waiting for monitor lock
  4. WAITING - Waiting indefinitely for another thread
  5. TIMED_WAITING - Waiting for specified time
  6. TERMINATED - Execution completed

3. Creating Threads in Java

Method 1: Extending Thread Class

class MyThread extends Thread { private String threadName; public MyThread(String name) { this.threadName = name; } @Override public void run() { System.out.println(threadName + " is running"); for (int i = 1; i <= 5; i++) { System.out.println(threadName + " - Count: " + i); try { Thread.sleep(500); // Simulate some work } catch (InterruptedException e) { System.out.println(threadName + " interrupted"); } } System.out.println(threadName + " finished"); } } public class ExtendThreadExample { public static void main(String[] args) { System.out.println("Main thread started"); MyThread thread1 = new MyThread("Thread-1"); MyThread thread2 = new MyThread("Thread-2"); thread1.start(); // Start thread1 thread2.start(); // Start thread2 try { thread1.join(); // Wait for thread1 to complete thread2.join(); // Wait for thread2 to complete } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Main thread finished"); } }

Method 2: Implementing Runnable Interface

class MyRunnable implements Runnable { private String threadName; public MyRunnable(String name) { this.threadName = name; } @Override public void run() { System.out.println(threadName + " is running"); for (int i = 1; i <= 5; i++) { System.out.println(threadName + " - Count: " + i); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println(threadName + " interrupted"); } } System.out.println(threadName + " finished"); } } public class RunnableExample { public static void main(String[] args) { System.out.println("Main thread started"); Thread thread1 = new Thread(new MyRunnable("Runnable-1")); Thread thread2 = new Thread(new MyRunnable("Runnable-2")); Thread thread3 = new Thread(() -> { System.out.println("Lambda thread running"); for (int i = 1; i <= 3; i++) { System.out.println("Lambda - Count: " + i); } }); thread1.start(); thread2.start(); thread3.start(); try { thread1.join(); thread2.join(); thread3.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Main thread finished"); } }

Method 3: Using Lambda Expressions (Java 8+)

public class LambdaThreadExample { public static void main(String[] args) { System.out.println("=== Lambda Threads ==="); // Simple lambda thread Thread lambdaThread = new Thread(() -> { System.out.println("Lambda thread executed by: " + Thread.currentThread().getName()); }); // Multiple lambda threads for (int i = 1; i <= 5; i++) { final int threadId = i; Thread thread = new Thread(() -> { System.out.println("Thread " + threadId + " executed by: " + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); thread.start(); } lambdaThread.start(); } }

4. Thread Methods and Control

public class ThreadMethodsDemo { public static void main(String[] args) throws InterruptedException { System.out.println("=== Thread Methods Demo ==="); Thread worker1 = new Thread(() -> { System.out.println("Worker 1 started"); try { for (int i = 1; i <= 10; i++) { System.out.println("Worker 1 - " + i); Thread.sleep(200); } } catch (InterruptedException e) { System.out.println("Worker 1 interrupted!"); } }); Thread worker2 = new Thread(() -> { System.out.println("Worker 2 started"); for (int i = 1; i <= 5; i++) { System.out.println("Worker 2 - " + i); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } }); // Get main thread reference Thread mainThread = Thread.currentThread(); System.out.println("Main thread name: " + mainThread.getName()); System.out.println("Main thread priority: " + mainThread.getPriority()); // Set thread properties worker1.setName("Worker-Thread-1"); worker2.setName("Worker-Thread-2"); worker1.setPriority(Thread.MAX_PRIORITY); // 10 worker2.setPriority(Thread.MIN_PRIORITY); // 1 worker1.start(); worker2.start(); // Demonstrate sleep System.out.println("Main thread sleeping for 1 second..."); Thread.sleep(1000); // Demonstrate interrupt worker1.interrupt(); // Check if threads are alive System.out.println("Worker1 alive: " + worker1.isAlive()); System.out.println("Worker2 alive: " + worker2.isAlive()); // Wait for worker2 to complete worker2.join(); System.out.println("Worker2 completed, alive: " + worker2.isAlive()); // Demonstate yield Thread.yield(); // Hint to scheduler to give other threads CPU time System.out.println("Main thread finished"); } }

5. Thread Synchronization

The Problem: Race Condition

class Counter { private int count = 0; public void increment() { count++; } public int getCount() { return count; } } public class RaceConditionExample { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); 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(); // Expected: 2000, but often gets less due to race condition System.out.println("Final count: " + counter.getCount()); } }

Solution 1: synchronized Method

class SynchronizedCounter { private int count = 0; // synchronized method public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } public class SynchronizedMethodExample { public static void main(String[] args) throws InterruptedException { SynchronizedCounter counter = new SynchronizedCounter(); 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(); // Always 2000 System.out.println("Final count: " + counter.getCount()); } }

Solution 2: synchronized Block

class BlockCounter { private int count = 0; private final Object lock = new Object(); // Lock object public void increment() { synchronized(lock) { count++; } } public int getCount() { synchronized(lock) { return count; } } }

Solution 3: ReentrantLock

import java.util.concurrent.locks.*; class LockCounter { private int count = 0; private final ReentrantLock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); // Always unlock in finally block } } public int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } }

6. Thread Communication - wait() and notify()

class SharedResource { private int data; private boolean available = false; public synchronized void produce(int value) throws InterruptedException { while (available) { wait(); // Wait for consumer to consume } data = value; available = true; System.out.println("Produced: " + value); notify(); // Notify consumer } public synchronized int consume() throws InterruptedException { while (!available) { wait(); // Wait for producer to produce } available = false; System.out.println("Consumed: " + data); notify(); // Notify producer return data; } } public class ProducerConsumerExample { public static void main(String[] args) { SharedResource resource = new SharedResource(); // Producer thread Thread producer = new Thread(() -> { try { for (int i = 1; i <= 5; i++) { resource.produce(i); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } }); // Consumer thread Thread consumer = new Thread(() -> { try { for (int i = 1; i <= 5; i++) { resource.consume(); Thread.sleep(1500); } } catch (InterruptedException e) { e.printStackTrace(); } }); producer.start(); consumer.start(); try { producer.join(); consumer.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }

7. Real-World Example: Bank Account Simulation

class BankAccount { private double balance; private final Object lock = new Object(); public BankAccount(double initialBalance) { this.balance = initialBalance; } public void deposit(double amount) { synchronized(lock) { balance += amount; System.out.println(Thread.currentThread().getName() + " deposited: " + amount + ", Balance: " + balance); lock.notifyAll(); // Notify waiting threads } } public void withdraw(double amount) throws InterruptedException { synchronized(lock) { while (balance < amount) { System.out.println(Thread.currentThread().getName() + " waiting to withdraw: " + amount); lock.wait(); // Wait for sufficient funds } balance -= amount; System.out.println(Thread.currentThread().getName() + " withdrew: " + amount + ", Balance: " + balance); } } public double getBalance() { synchronized(lock) { return balance; } } } public class BankAccountExample { public static void main(String[] args) throws InterruptedException { BankAccount account = new BankAccount(1000); // Withdrawal threads Thread withdraw1 = new Thread(() -> { try { account.withdraw(800); } catch (InterruptedException e) { e.printStackTrace(); } }, "Withdraw-1"); Thread withdraw2 = new Thread(() -> { try { account.withdraw(700); } catch (InterruptedException e) { e.printStackTrace(); } }, "Withdraw-2"); // Deposit threads Thread deposit1 = new Thread(() -> { account.deposit(500); }, "Deposit-1"); Thread deposit2 = new Thread(() -> { account.deposit(300); }, "Deposit-2"); withdraw1.start(); withdraw2.start(); Thread.sleep(1000); // Let withdrawal threads wait deposit1.start(); deposit2.start(); withdraw1.join(); withdraw2.join(); deposit1.join(); deposit2.join(); System.out.println("Final balance: " + account.getBalance()); } }

8. Thread Pools with ExecutorService

import java.util.concurrent.*; public class ThreadPoolExample { public static void main(String[] args) { System.out.println("=== Thread Pool Examples ==="); // 1. Fixed Thread Pool ExecutorService fixedPool = Executors.newFixedThreadPool(3); System.out.println("Fixed Thread Pool (3 threads):"); for (int i = 1; i <= 6; i++) { final int taskId = i; fixedPool.execute(() -> { System.out.println(Thread.currentThread().getName() + " executing task " + taskId); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); } fixedPool.shutdown(); try { fixedPool.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } // 2. Cached Thread Pool ExecutorService cachedPool = Executors.newCachedThreadPool(); System.out.println("\nCached Thread Pool:"); for (int i = 1; i <= 10; i++) { final int taskId = i; cachedPool.submit(() -> { System.out.println(Thread.currentThread().getName() + " executing task " + taskId); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } }); } cachedPool.shutdown(); // 3. Scheduled Thread Pool ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2); System.out.println("\nScheduled Thread Pool:"); scheduledPool.schedule(() -> { System.out.println("Task executed after 2 seconds delay"); }, 2, TimeUnit.SECONDS); scheduledPool.scheduleAtFixedRate(() -> { System.out.println("Repeated task executed every 1 second"); }, 1, 1, TimeUnit.SECONDS); // Let it run for 5 seconds then shutdown try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } scheduledPool.shutdown(); } }

9. Callable and Future

import java.util.concurrent.*; import java.util.*; public class CallableFutureExample { public static void main(String[] args) throws Exception { System.out.println("=== Callable and Future ==="); ExecutorService executor = Executors.newFixedThreadPool(3); // Callable that returns a result Callable<Integer> factorialTask = () -> { int number = 5; int result = 1; for (int i = 1; i <= number; i++) { result *= i; Thread.sleep(500); // Simulate computation } return result; }; Callable<String> stringTask = () -> { Thread.sleep(1000); return "Hello from Callable!"; }; Callable<Double> randomTask = () -> { Thread.sleep(800); return Math.random() * 100; }; // Submit tasks and get Futures Future<Integer> factorialFuture = executor.submit(factorialTask); Future<String> stringFuture = executor.submit(stringTask); Future<Double> randomFuture = executor.submit(randomTask); // Check if tasks are done System.out.println("Factorial task done: " + factorialFuture.isDone()); System.out.println("String task done: " + stringFuture.isDone()); // Get results (blocks until available) System.out.println("Factorial result: " + factorialFuture.get()); System.out.println("String result: " + stringFuture.get()); System.out.println("Random result: " + randomFuture.get()); // Multiple tasks with invokeAll List<Callable<String>> tasks = Arrays.asList( () -> { Thread.sleep(1000); return "Task 1"; }, () -> { Thread.sleep(500); return "Task 2"; }, () -> { Thread.sleep(800); return "Task 3"; } ); System.out.println("\n=== invokeAll Example ==="); List<Future<String>> futures = executor.invokeAll(tasks); for (Future<String> future : futures) { System.out.println("Result: " + future.get()); } executor.shutdown(); } }

10. Common Multithreading Issues and Solutions

public class CommonIssues { // 1. Deadlock Example private static final Object lock1 = new Object(); private static final Object lock2 = new Object(); public static void demonstrateDeadlock() { Thread t1 = new Thread(() -> { synchronized(lock1) { System.out.println("Thread 1: Holding lock 1..."); try { Thread.sleep(100); } catch (InterruptedException e) {} System.out.println("Thread 1: Waiting for lock 2..."); synchronized(lock2) { System.out.println("Thread 1: Acquired both locks!"); } } }); Thread t2 = new Thread(() -> { synchronized(lock2) { System.out.println("Thread 2: Holding lock 2..."); try { Thread.sleep(100); } catch (InterruptedException e) {} System.out.println("Thread 2: Waiting for lock 1..."); synchronized(lock1) { System.out.println("Thread 2: Acquired both locks!"); } } }); t1.start(); t2.start(); } // 2. Livelock Example static class Spoon { private Diner owner; public Spoon(Diner d) { owner = d; } public synchronized void setOwner(Diner d) { owner = d; } public synchronized void use() { System.out.println(owner.name + " is using the spoon!"); } } static class Diner { private String name; private boolean isHungry; public Diner(String n) { name = n; isHungry = true; } public void eatWith(Spoon spoon, Diner spouse) { while (isHungry) { if (spoon.owner != this) { try { Thread.sleep(1); } catch (InterruptedException e) {} continue; } if (spouse.isHungry) { System.out.println(name + ": You eat first, " + spouse.name); spoon.setOwner(spouse); continue; } spoon.use(); isHungry = false; System.out.println(name + ": I'm done eating"); spoon.setOwner(spouse); } } } public static void demonstrateLivelock() { final Diner husband = new Diner("Husband"); final Diner wife = new Diner("Wife"); final Spoon spoon = new Spoon(husband); new Thread(() -> husband.eatWith(spoon, wife)).start(); new Thread(() -> wife.eatWith(spoon, husband)).start(); } } public class IssuesDemo { public static void main(String[] args) throws InterruptedException { System.out.println("=== Common Multithreading Issues ==="); // Uncomment to see deadlock (will freeze) // CommonIssues.demonstrateDeadlock(); // Uncomment to see livelock // CommonIssues.demonstrateLivelock(); // 3. Starvation Example Object sharedResource = new Object(); Thread greedyThread = new Thread(() -> { synchronized(sharedResource) { while (true) { try { Thread.sleep(1000); System.out.println("Greedy thread holding lock..."); } catch (InterruptedException e) { break; } } } }); Thread starvedThread = new Thread(() -> { // This thread will starve synchronized(sharedResource) { System.out.println("Starved thread finally got the lock!"); } }); greedyThread.setDaemon(true); // So it doesn't prevent JVM exit greedyThread.start(); Thread.sleep(100); starvedThread.start(); // Let it run for a bit Thread.sleep(3000); starvedThread.interrupt(); } }

11. Best Practices

public class MultithreadingBestPractices { // 1. Use thread pools instead of creating threads manually public static void useThreadPools() { ExecutorService executor = Executors.newFixedThreadPool(10); for (int i = 0; i < 100; i++) { final int taskId = i; executor.submit(() -> { System.out.println("Executing task " + taskId + " in " + Thread.currentThread().getName()); }); } executor.shutdown(); } // 2. Prefer immutability static final class ImmutableValue { private final int value; public ImmutableValue(int value) { this.value = value; } public int getValue() { return value; } public ImmutableValue add(int newValue) { return new ImmutableValue(this.value + newValue); } } // 3. Use concurrent collections public static void useConcurrentCollections() { ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); // Thread-safe operations map.put("key", 1); list.add("value"); } // 4. Proper resource cleanup static class ResourceHandler implements AutoCloseable { private final ExecutorService executor; public ResourceHandler() { this.executor = Executors.newFixedThreadPool(5); } public void executeTask(Runnable task) { executor.submit(task); } @Override public void close() { executor.shutdown(); try { if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); } } } } public class BestPracticesDemo { public static void main(String[] args) { System.out.println("=== Best Practices ==="); // 1. Always use try-with-resources for cleanup try (MultithreadingBestPractices.ResourceHandler handler = new MultithreadingBestPractices.ResourceHandler()) { handler.executeTask(() -> System.out.println("Task executed")); } // Automatic cleanup // 2. Use meaningful thread names Thread worker = new Thread(() -> { System.out.println("Working..."); }, "File-Processor-Thread"); worker.start(); // 3. Handle interrupts properly Thread interruptibleThread = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { try { System.out.println("Working..."); Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("Thread interrupted, cleaning up..."); Thread.currentThread().interrupt(); // Preserve interrupt status break; } } }); interruptibleThread.start(); try { Thread.sleep(3000); interruptibleThread.interrupt(); interruptibleThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }

12. Conclusion

Key Takeaways:

  1. Thread Creation: Extend Thread, implement Runnable, or use lambda
  2. Synchronization: Use synchronized blocks/methods or Lock objects
  3. Thread Communication: wait(), notify(), notifyAll()
  4. Thread Pools: Use ExecutorService for better resource management
  5. Callable/Future: For tasks that return results

Best Practices:

  • ✅ Use thread pools instead of creating threads manually
  • ✅ Prefer implementing Runnable over extending Thread
  • ✅ Always use proper synchronization for shared resources
  • ✅ Handle interrupts properly
  • ✅ Use concurrent collections when possible
  • ✅ Clean up resources properly

Common Pitfalls:

  • ❌ Race conditions (missing synchronization)
  • ❌ Deadlocks (circular wait for locks)
  • ❌ Livelocks (threads busy but not progressing)
  • ❌ Resource leakage (not shutting down executors)
  • ❌ Ignoring interrupts

When to Use Multithreading:

  • CPU-intensive tasks that can run in parallel
  • I/O operations that involve waiting
  • Background tasks in GUI applications
  • Processing large datasets
  • Handling multiple client requests

Final Thoughts:

Multithreading is a powerful feature but comes with complexity. Start with simple examples, understand the basics thoroughly, and always test your multithreaded code extensively. Modern Java provides excellent concurrency utilities - leverage them instead of reinventing the wheel.

Master multithreading to build high-performance, responsive applications that make the most of modern multi-core processors!

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper