Multithreading
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();- Thread = an independent execution path
- Parallel work happens at the same time
- synchronized keeps data safe
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 Thread | implements Runnable | |
|---|---|---|
| Can extend another class? | No | Yes |
| Recommended? | Less | More (best practice) |
| Use with ExecutorService | Hard | Easy |
class MyTask implements Runnable {
public void run() { System.out.println("Running!"); }
}
Thread t = new Thread(new MyTask());
t.start();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
}