🏃‍♂️
Multithreading

Multithreading

Doing Many Things at Once
💡 A single thread is a lone runner doing everything one by one. Multithreading is many runners racing on different tracks at once — the work gets done faster!

A thread is an independent flow within a program. Normally, Java runs in a single thread (main).

You can create new threads by extending Thread or implementing Runnable, which then do work in parallel.

class MyThread extends Thread {
  public void run() { System.out.println("Running..."); }
}
new MyThread().start();
🏃‍♂️
A single thread is a lone runner doing everything one by one. Multithreading is many runners racing on different tracks at once — the work gets done faster!
1 / 3
⚡ Quick Recap
  • Thread = an independent execution path
  • Parallel work happens at the same time
  • synchronized keeps data safe
On this page (4 subtopics)

Extend the Thread class and override run() — simple, but this uses up your "extends" slot (Java has single inheritance, so your class can't extend anything else anymore).

Or implement the Runnable interface and pass it to a Thread — this is better practice since the class can still extend something else, and the behavior (Runnable — "what to do") stays separate from the thread (Thread — "how to run it"). Modern Java mostly recommends the Runnable approach.

extends Threadimplements Runnable
Can extend another class?NoYes
Recommended?LessMore (best practice)
Use with ExecutorServiceHardEasy
class MyTask implements Runnable {
  public void run() { System.out.println("Running!"); }
}
Thread t = new Thread(new MyTask());
t.start();
⚠️Common Mistake: Calling t.run() and calling t.start() are completely different! run() is just a normal method call (runs on the same thread, no new thread is created). start() is what actually creates a new thread that runs run() in parallel.

Every thread has a priority (1 to 10, default 5, changeable with setPriority()) — it gives the JVM a "hint" about which thread should get more CPU time (not a guarantee, OS-dependent, many OSes may ignore this hint entirely).

Daemon threads are background service threads (the Garbage Collector itself is a daemon thread) — once all non-daemon (normal) threads finish, the JVM exits without caring about daemon threads. Use setDaemon(true) to make a thread a daemon (must be called before start()).

Thread.sleep(ms) "pauses" the current thread for a while — it's not waiting for any other thread, just passing its own time (it's a static method, affects only the current thread).

t.join() makes one thread wait until *another* thread (t) finishes — like when the main thread wants a worker thread to finish its job first before proceeding. This is useful when a result from one thread is needed by another.

Thread worker = new Thread(() -> doWork());
worker.start();
worker.join(); // main thread yaha wait karega worker khatam hone tak
System.out.println("Worker done, ab aage badho");

When multiple threads try to change a shared variable simultaneously without proper synchronization, the result becomes unpredictable — this is called a "race condition".

Say two threads are doing count++ on a counter: count++ is actually 3 steps (read the current value, add 1, write it back) — if both threads read the old value at the same time, both will write their own increment, but one update gets "lost" (the final value ends up lower than expected).

// DANGEROUS without synchronization:
class Counter {
  int count = 0;
  void increment() { count++; } // NOT atomic! race condition risk
}
⚠️Common Mistake: count++ is NOT a single, atomic operation — it's read-modify-write. In a multi-threaded environment this can cause a race condition. Fix it with synchronized or AtomicInteger (detailed in the next topic).