The 3LEVEL method means:
- 🟢 LEVEL 1 — Beginner: What is it?
- 🟡 LEVEL 2 — Intermediate: How does it work?
- 🔴 LEVEL 3 — Advanced: What happens internally and what problems must you handle?
Multithreading is the process of executing multiple threads concurrently within a single process.
A thread is a lightweight unit of execution.
Java Process
|
├── Thread 1
├── Thread 2
└── Thread 3
A Java application may simultaneously:
Download a file
Play music
Handle user input
Instead of making one task wait unnecessarily for another, multiple threads can make progress concurrently.
Every normal Java application begins execution with the main thread.
class Demo
{
public static void main(String[] args)
{
System.out.println(
Thread.currentThread().getName()
);
}
}Output:
main
So:
JVM
↓
main thread
↓
main()
There are two classic ways.
class MyThread extends Thread
{
public void run()
{
System.out.println("Child Thread");
}
}
class Demo
{
public static void main(String[] args)
{
MyThread t = new MyThread();
t.start();
}
}class MyTask implements Runnable
{
public void run()
{
System.out.println("Child Thread");
}
}
class Demo
{
public static void main(String[] args)
{
MyTask task = new MyTask();
Thread t = new Thread(task);
t.start();
}
}This is one of the most important points.
t.start();Starts a new thread of execution, which then invokes run().
t.run();is simply a normal method invocation.
It does not by itself create a new thread.
start()
↓
new thread execution
↓
run()
Whereas:
run()
↓
normal method call
A thread can move through these Java Thread.State values:
NEW
↓
RUNNABLE
↓
BLOCKED / WAITING / TIMED_WAITING
↓
RUNNABLE
↓
TERMINATED
| State | Meaning |
|---|---|
| NEW | Thread created but not started |
| RUNNABLE | Eligible to run / running |
| BLOCKED | Waiting to acquire a monitor lock |
| WAITING | Waiting indefinitely for another action |
| TIMED_WAITING | Waiting for a specified time |
| TERMINATED | Execution has completed |
Thread.sleep(1000);Pauses the current thread for approximately one second.
Important: sleep() does not release an intrinsic monitor.
t.join();Makes the calling thread wait until thread t terminates.
t.getName();Gets the thread name.
t.setName("Worker");Sets the thread name.
Thread.currentThread();Returns the currently executing thread.
The biggest problem begins when multiple threads access shared mutable data.
Example:
class Counter
{
int count = 0;
void increment()
{
count++;
}
}Suppose two threads execute:
Thread 1 → increment()
Thread 2 → increment()
We might expect:
0 → 1 → 2
But count++ conceptually involves:
READ
↓
ADD 1
↓
WRITE
Two threads can interleave those operations.
This can cause a:
We can protect the critical section:
class Counter
{
int count = 0;
synchronized void increment()
{
count++;
}
}Now the relevant monitor permits only one thread at a time to execute the synchronized method for that object.
Thread 1
↓
gets monitor
↓
increment()
↓
releases monitor
Thread 2
↓
gets monitor
↓
increment()
Instead of synchronizing the complete method:
class Counter
{
int count = 0;
void increment()
{
synchronized(this)
{
count++;
}
}
}Only the critical section is protected.
You can also synchronize on a dedicated lock:
class Counter
{
private int count = 0;
private final Object lock = new Object();
void increment()
{
synchronized(lock)
{
count++;
}
}
}Every Java object can be associated with an intrinsic monitor.
Think of it as a lock:
Object
|
↓
Monitor
|
↓
One thread owns it at a time
For an instance synchronized method:
synchronized void test()
{
}the relevant object's monitor is used.
For:
static synchronized void test()
{
}the synchronization is associated with the Class object's monitor.
This difference is extremely important.
sleep() |
wait() |
|---|---|
Method of Thread |
Method of Object |
| Pauses current thread | Used for thread coordination |
| Does not release intrinsic monitor | Releases the corresponding monitor |
| Can be used with a timeout | Can be indefinite or timed |
| Doesn't require owning a monitor | Must be called while owning the corresponding monitor |
Example:
synchronized(lock)
{
lock.wait();
}When wait() is called:
Thread
↓
wait()
↓
releases lock
↓
WAITING
synchronized(lock)
{
lock.notify();
}Makes one waiting thread eligible to compete to reacquire that monitor.
synchronized(lock)
{
lock.notifyAll();
}Makes all threads waiting on that monitor eligible to compete for the monitor.
It does not mean all those threads execute the synchronized section simultaneously.
A classic use of wait() and notifyAll() is Producer–Consumer.
Producer
|
↓
Buffer
|
↓
Consumer
If the buffer is full:
Producer → waits
If the buffer is empty:
Consumer → waits
When the state changes:
notifyAll()
can wake waiting threads so they can re-check their conditions.
Correct coordination commonly looks like:
synchronized(lock)
{
while(!condition)
{
lock.wait();
}
// perform operation
}Use while, not if, to re-check the condition after waking.
Java defines:
MIN_PRIORITY = 1
NORM_PRIORITY = 5
MAX_PRIORITY = 10
Example:
t.setPriority(Thread.MAX_PRIORITY);But priority does not guarantee that a particular thread executes first.
A daemon thread is a background thread.
Thread t = new Thread(task);
t.setDaemon(true);
t.start();setDaemon(true) must be called before starting the thread.
The JVM does not remain alive solely because daemon threads are still running after all non-daemon threads have terminated.
Synchronization is not only:
"Allow one thread at a time."
It also provides important memory visibility and ordering guarantees.
So synchronization gives us, among other things:
Mutual Exclusion
+
Memory Visibility / Ordering
synchronized void test()
{
}Uses the relevant object's monitor.
Object A → Monitor A
Object B → Monitor B
So two different objects have different intrinsic monitors.
static synchronized void test()
{
}Uses the monitor associated with the class object.
Conceptually:
Class object
|
Monitor
|
static synchronized method
Suppose one thread changes a shared flag:
volatile boolean running = true;volatile helps ensure that reads and writes of that variable have the required cross-thread visibility semantics.
But:
volatile int count;
count++;is not an atomic increment.
Remember:
volatile
↓
visibility
synchronized
↓
mutual exclusion
+
visibility/order
For atomic operations, Java provides classes such as:
AtomicInteger
AtomicLong
AtomicBooleanExample:
import java.util.concurrent.atomic.AtomicInteger;
class Counter
{
AtomicInteger count = new AtomicInteger();
void increment()
{
count.incrementAndGet();
}
}Here:
count.incrementAndGet();performs an atomic increment.
Deadlock occurs when threads become permanently stuck waiting for resources held by one another.
Example:
Thread 1
|
owns Lock A
|
wants Lock B
↑
|
owns Lock B
|
Thread 2
|
wants Lock A
Neither can continue.
T1 → A → waits for B
T2 → B → waits for A
A thread suffers starvation when it repeatedly fails to obtain the resources or execution opportunities it needs.
Thread 1 → repeatedly gets resource
Thread 2 → keeps waiting
It is different from deadlock because the system may still be making progress elsewhere.
In livelock, threads remain active but fail to make useful progress.
Thread 1 → changes action
Thread 2 → reacts
Thread 1 → reacts again
Thread 2 → reacts again
They are not blocked, but the actual task does not complete.
For real-world applications, manually creating large numbers of threads is often inappropriate.
Use an executor:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Demo
{
public static void main(String[] args)
{
ExecutorService service =
Executors.newFixedThreadPool(2);
service.submit(() ->
{
System.out.println("Task 1");
});
service.submit(() ->
{
System.out.println("Task 2");
});
service.shutdown();
}
}Architecture:
Tasks
↓
ExecutorService
↓
Thread Pool
↓
Worker Threads
Runnable r = () ->
{
System.out.println("Hello");
};Generally represents a task that doesn't return a result.
Callable<Integer> c = () ->
{
return 100;
};Can return a result and throw checked exceptions.
Future<Integer> result =
service.submit(c);Then:
Integer value = result.get();get() waits if necessary until the result is available.
MULTITHREADING
|
┌───────────────┼────────────────┐
↓ ↓ ↓
LEVEL 1 LEVEL 2 LEVEL 3
Beginner Intermediate Advanced
| | |
↓ ↓ ↓
Thread Shared Data Memory Model
Process Race Condition volatile
main Synchronization Atomic Classes
start() synchronized Deadlock
run() Monitor Starvation
Runnable wait() Livelock
Lifecycle notify() ExecutorService
sleep() notifyAll() Thread Pool
join() Producer/Consumer Callable
Priority Future
Daemon
Thread
↓
Lightweight execution unit
Multithreading
↓
Multiple threads within a process
Create
↓
Thread / Runnable
Start
↓
start()
Task
↓
run()
Multiple Threads
↓
Shared Data
↓
Race Condition
↓
Critical Section
↓
synchronized
↓
Monitor
↓
wait()
notify()
notifyAll()
Shared Memory
|
├── Race Condition
├── Visibility
├── Atomicity
├── Deadlock
├── Starvation
└── Livelock
Solutions
|
├── synchronized
├── volatile
├── Atomic classes
├── Locks
├── Concurrent collections
└── ExecutorService
Thread → Shared Data → Race Condition → Synchronization → Monitor → wait/notify → Thread Safety → Deadlock/Starvation/Livelock → Modern Concurrency Utilities.