1. What is Multithreading?

  • Multitasking: Performing multiple tasks simultaneously.

    • Process-based: Multiple programs (e.g., browser + music player).
    • Thread-based: Multiple parts of a single program.
  • Thread: Smallest unit of execution inside a process.
  • Multithreading: Running multiple threads concurrently to improve performance.
👉 Java provides built-in support for multithreading (java.lang.Thread, Runnable).


2. Thread Lifecycle (States)

A thread goes through these states:

  1. New → Created but not started.
  2. Runnable → Ready to run (after start()).
  3. Running → Thread scheduler picks it.
  4. Waiting/Timed Waiting → Waiting for another thread (via join(), sleep(), etc.).
  5. Terminated → Thread finishes execution.


3. Creating Threads

(a) Extending Thread class

class MyThread extends Thread {
    public void run() {
        System.out.println("Thread running: " + Thread.currentThread().getName());
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start(); // starts new thread
    }
}

(b) Implementing Runnable interface

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Thread running: " + Thread.currentThread().getName());
    }
}

public class RunnableDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
    }
}

👉 Runnable is preferred since Java allows multiple inheritance through interfaces.

(c) Using Lambda (Java 8+)

public class LambdaThreadDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> System.out.println("Thread running"));
        t1.start();
    }
}

4. Thread Methods

Method Description
start() Starts a new thread, calls run() internally.
run() Defines task, but doesn’t create a new thread if called directly.
sleep(ms) Pause thread for given milliseconds.
join() Waits for another thread to finish.
yield() Gives chance to other threads of equal priority.
setPriority() Set thread priority (1–10).
isAlive() Checks if thread is alive.

5. Thread Priority

  • Range: 1 (MIN)10 (MAX)
  • Default = 5 (NORM_PRIORITY)

Thread t1 = new Thread();
t1.setPriority(Thread.MAX_PRIORITY);

⚠️ Thread scheduling depends on OS + JVM, so priorities are only hints.


6. Thread Synchronization

Problem: Race condition – When multiple threads modify shared data at the same time.

Without Synchronization:

class Counter {
    int count = 0;
    public void increment() {
        count++;
    }
}

public class SyncDemo {
    public static void main(String[] args) throws Exception {
        Counter c = new Counter();

        Thread t1 = new Thread(() -> { for(int i=0;i<1000;i++) c.increment(); });
        Thread t2 = new Thread(() -> { for(int i=0;i<1000;i++) c.increment(); });

        t1.start(); t2.start();
        t1.join(); t2.join();

        System.out.println(c.count); // Expected: 2000, Actual: less (race condition!)
    }
}

With Synchronization:

class Counter {
    int count = 0;
    public synchronized void increment() {
        count++;
    }
}

👉 synchronized ensures only one thread executes the method at a time.


7. Inter-thread Communication

Threads can communicate using:

(a) wait(), notify(), notifyAll()

class Shared {
    boolean ready = false;

    synchronized void produce() {
        System.out.println("Producing...");
        ready = true;
        notify(); // notify waiting thread
    }

    synchronized void consume() throws InterruptedException {
        while(!ready) {
            wait(); // wait until producer notifies
        }
        System.out.println("Consumed!");
    }
}

8. Deadlock

When two or more threads wait forever for resources.

Example:

class A { synchronized void methodA(B b) { b.last(); } synchronized void last() {} }
class B { synchronized void methodB(A a) { a.last(); } synchronized void last() {} }

👉 Always acquire locks in the same order to avoid deadlocks.


9. Thread Pools (Executor Framework)

Instead of creating threads manually, use Executors.

import java.util.concurrent.*;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);

        for (int i = 1; i <= 5; i++) {
            int taskId = i;
            executor.execute(() -> System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName()));
        }

        executor.shutdown();
    }
}

👉 Efficient, reusable threads → avoids performance overhead.


10. Advanced Concepts

  • Callable & Future: Return results from threads.
  • ReentrantLock: Advanced synchronization mechanism.
  • Atomic Variables: AtomicInteger, AtomicBoolean for lock-free thread safety.
  • Concurrent Collections: ConcurrentHashMap, CopyOnWriteArrayList.


11. Interview-Focused FAQs

Q1: Difference between Thread and Runnable?

  • Thread: Extends class, less flexible.
  • Runnable: Implements interface, preferred.

Q2: What is synchronization?

  • Mechanism to prevent race conditions by allowing one thread at a time.

Q3: What is deadlock? How to prevent it?

  • Two threads waiting for each other → infinite wait.
  • Prevent by acquiring locks in same order or using tryLock().

Q4: What is difference between sleep() and wait()?

  • sleep() → doesn’t release lock.
  • wait() → releases lock, used for inter-thread communication.

Q5: Difference between ExecutorService and creating threads manually?

  • ExecutorService manages a pool of threads → more efficient.

12. Best Practices

  • Use Executor framework over manually managing threads.
  • Minimize use of synchronized → consider Concurrent classes.
  • Always handle InterruptedException.
  • Avoid deadlocks → acquire locks in order.
  • Use atomic classes for simple counters.

Next: Chapter 20 – Java Collections Framework (Master Guide)