This is the complete DEEPDIVE version, following the same approach as Exception Handling: definitions, concepts, terminology, diagrams, programs, outputs, confusing points, synchronization, inter-thread communication, problems, and modern Java concurrency.
Multithreading is the process of executing multiple threads concurrently within a single Java process.
A thread is the smallest unit of execution that can be scheduled independently.
For example, a browser-like application may need to:
Download data
+
Process data
+
Respond to user actions
+
Perform background work
Instead of making one activity wait for another unnecessarily, different tasks can be performed by different threads.
Java Process
|
┌──────────────┼──────────────┐
↓ ↓ ↓
Thread 1 Thread 2 Thread 3
Download Calculation User Input
A process is a program in execution.
For example:
Java Application
↓
Process
|
┌─────┼─────┐
↓ ↓ ↓
T1 T2 T3
A single process can contain multiple threads.
| Process | Thread |
|---|---|
| Program in execution | Unit of execution inside a process |
| Has its own address space | Shares process resources |
| Relatively heavyweight | Relatively lightweight |
| Process creation is more expensive | Thread creation is generally cheaper |
| Processes normally communicate through IPC mechanisms | Threads can communicate through shared memory |
| One process can contain multiple threads | A thread belongs to a process |
Suppose we have:
task1();
task2();
task3();With a single thread:
task1
↓
task2
↓
task3
If task1() takes a long time, task2() must wait.
With multiple threads:
Thread 1 → task1
Thread 2 → task2
Thread 3 → task3
This can improve:
- Responsiveness
- Throughput
- Resource utilization
- Background processing
- Concurrent task execution
Multithreading does not automatically mean faster execution.
Creating and coordinating threads also has overhead.
These terms are often confused.
Multiple tasks make progress during overlapping periods.
Time →
T1: ███ ███
T2: ███ ███
Multiple tasks literally execute simultaneously on different CPU cores.
Core 1: █████████
Core 2: █████████
Therefore:
Concurrency ≠ necessarily Parallelism
Java supports both depending on the environment and execution model.
A thread is an independent path of execution within a process.
Every Java application starts with at least one thread: the main thread.
Example:
class Demo
{
public static void main(String[] args)
{
System.out.println(
Thread.currentThread().getName()
);
}
}Typical output:
main
When the JVM starts the application's main() method, execution occurs on the main thread.
JVM
|
↓
main thread
|
↓
main()
From the main thread, we can create additional threads.
There are two traditional approaches commonly taught first:
- Extend
Thread - Implement
Runnable
class MyThread extends Thread
{
public void run()
{
System.out.println("Child thread running");
}
}
class Demo
{
public static void main(String[] args)
{
MyThread t = new MyThread();
t.start();
System.out.println("Main thread running");
}
}Possible output:
Child thread running
Main thread running
or:
Main thread running
Child thread running
Because thread scheduling is not guaranteed to follow source-code order after start().
This is one of the most important multithreading concepts.
t.start();does not simply execute run() like an ordinary method call.
It starts the thread's execution and the JVM schedules that thread to execute its run() method.
Conceptually:
t.start()
↓
Thread becomes eligible to run
↓
Scheduler/JVM
↓
run()
Consider:
class Demo extends Thread
{
public void run()
{
System.out.println(
Thread.currentThread().getName()
);
}
public static void main(String[] args)
{
Demo t = new Demo();
t.run();
}
}Output:
main
Why?
Because:
t.run();is simply a normal method invocation.
It does not create a new thread.
Now:
t.start();causes the thread to be started.
start()
↓
new thread execution
run()
↓
normal method execution when called directly
Never confuse these two.
Runnable represents a task whose run() method can be executed by a thread.
class MyTask implements Runnable
{
public void run()
{
System.out.println("Task running");
}
}
class Demo
{
public static void main(String[] args)
{
MyTask task = new MyTask();
Thread t = new Thread(task);
t.start();
}
}Output:
Task running
Java allows a class to extend only one class.
If we write:
class MyTask extends Thread
{
}our class is already using its class inheritance relationship with Thread.
With:
class MyTask implements Runnable
{
}the task is separated from the thread object.
Conceptually:
Task
↓
Runnable
Execution mechanism
↓
Thread
This separation is often cleaner.
Java represents thread states using Thread.State.
The states are:
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
When a thread object is created:
Thread t = new Thread(task);it is in the:
NEW
state.
Thread object created
↓
NEW
The thread has not yet been started.
After:
t.start();the thread becomes eligible for execution.
NEW
↓
start()
↓
RUNNABLE
RUNNABLE covers a thread that is ready to run as well as one actually running according to the JVM's state model.
A thread enters BLOCKED when it is waiting to acquire an intrinsic monitor lock.
Example:
Thread 1
|
owns lock
|
Thread 2
|
waiting for same lock
↓
BLOCKED
A thread enters WAITING when it waits indefinitely for another thread/action.
Examples include:
wait();or certain forms of:
join();A thread enters TIMED_WAITING when it waits for a specified period.
Examples:
Thread.sleep(1000);and timed forms of:
wait(1000);
join(1000);After the thread's run() method finishes:
Thread execution completed
↓
TERMINATED
A terminated thread cannot be restarted.
NEW
|
start()
↓
RUNNABLE
/ | \
↓ ↓ ↓
BLOCKED WAITING TIMED_WAITING
\ | /
\ | /
RUNNABLE
|
run() completes
↓
TERMINATED
No.
This is invalid:
Thread t = new Thread(task);
t.start();
t.start();A thread instance can be started only once.
The second attempt results in:
IllegalThreadStateException
Some important methods include:
start()
run()
sleep()
join()
interrupt()
isAlive()
getName()
setName()
getPriority()
setPriority()
currentThread()
isInterrupted()
Returns a reference to the currently executing thread.
System.out.println(
Thread.currentThread().getName()
);class Demo extends Thread
{
public void run()
{
System.out.println(getName());
}
public static void main(String[] args)
{
Demo t = new Demo();
t.setName("Worker");
t.start();
}
}Output:
Worker
sleep() pauses the currently executing thread for approximately the specified duration.
class Demo extends Thread
{
public void run()
{
for(int i = 1; i <= 3; i++)
{
System.out.println(i);
try
{
Thread.sleep(1000);
}
catch(InterruptedException e)
{
System.out.println("Interrupted");
return;
}
}
}
public static void main(String[] args)
{
new Demo().start();
}
}Output:
1
2
3
The values appear approximately one second apart.
No.
Suppose a thread is executing inside a synchronized block and calls:
Thread.sleep(5000);it continues to hold the intrinsic monitor during the sleep.
This is an extremely important distinction:
sleep()
↓
pauses thread
↓
does NOT release monitor
join() makes the calling thread wait for another thread to terminate.
class Demo extends Thread
{
public void run()
{
System.out.println("Child started");
try
{
Thread.sleep(2000);
}
catch(InterruptedException e)
{
Thread.currentThread().interrupt();
}
System.out.println("Child completed");
}
public static void main(String[] args)
throws InterruptedException
{
Demo t = new Demo();
t.start();
t.join();
System.out.println("Main completed");
}
}Output:
Child started
Child completed
Main completed
If:
Main → t.join()
then:
Main
↓
waits for t
↓
t completes
↓
Main continues
join() does not mean "join two threads into one thread."
System.out.println(t.isAlive());Returns whether the thread is alive according to the Thread API.
Java defines:
Thread.MIN_PRIORITY
Thread.NORM_PRIORITY
Thread.MAX_PRIORITYwith values:
1
5
10
Example:
t.setPriority(Thread.MAX_PRIORITY);But:
Priority does not guarantee execution order.
A daemon thread is intended for background work.
Thread t = new Thread(task);
t.setDaemon(true);
t.start();Important:
setDaemon(true)
↓
must happen before start()
The JVM does not remain alive solely because daemon threads remain after all non-daemon threads have terminated.
A thread can request another thread to interrupt its current activity:
t.interrupt();It is important to understand:
interrupt()does not forcibly kill the thread.
It sets the interruption status and may cause interruptible blocking operations such as sleep(), wait(), or join() to throw InterruptedException.
Suppose:
class Counter
{
int count = 0;
void increment()
{
count++;
}
}Now two threads execute:
counter.increment();simultaneously.
Many beginners think:
count++
is one indivisible operation.
Conceptually, it involves:
READ count
↓
ADD 1
↓
WRITE count
Suppose:
Initial count = 0
Thread 1 reads 0
Thread 2 reads 0
Thread 1 writes 1
Thread 2 writes 1
Expected:
2
Actual:
1
This is a race condition.
A critical section is a section of code that accesses shared mutable state and must be protected against unsafe concurrent execution.
Example:
count++;can be part of a critical section.
Thread 1 ───┐
↓
Critical Section
↑
Thread 2 ───┘
Synchronization is a mechanism for controlling concurrent access to shared resources so that operations requiring mutual exclusion are not performed by multiple threads simultaneously.
Main goals include:
- Mutual exclusion
- Visibility/ordering guarantees
- Maintaining data consistency
Every Java object can be associated with an intrinsic monitor.
When a thread enters:
synchronized(obj)
{
// protected code
}it must acquire obj's monitor.
Object
|
Intrinsic Monitor
|
┌───────┴───────┐
↓ ↓
Thread 1 Thread 2
owns waits
lock
Only one thread at a time can own that particular monitor.
Example:
class Counter
{
private int count = 0;
synchronized void increment()
{
count++;
}
int getCount()
{
return count;
}
}If two threads invoke increment() on the same Counter object, only one can execute that synchronized method at a time.
For an instance synchronized method:
synchronized void increment()
{
}the lock is associated with:
this object
Conceptually:
counter
|
└── monitor
|
├── Thread 1 → owns lock
|
└── Thread 2 → waits
Consider:
Counter c1 = new Counter();
Counter c2 = new Counter();If:
Thread 1 → c1.increment()
Thread 2 → c2.increment()
they are using different object monitors.
Therefore, synchronization on c1 does not automatically block synchronization on c2.
This is a major source of confusion.
Instead of synchronizing the complete method:
synchronized void test()
{
}we can protect only a critical section:
void test()
{
// non-critical code
synchronized(this)
{
// critical section
}
// other code
}This can reduce unnecessary locking.
Instead of:
synchronized(this)we can use:
private final Object lock = new Object();
void increment()
{
synchronized(lock)
{
count++;
}
}This can provide better encapsulation of the lock.
Consider:
static synchronized void test()
{
}This synchronizes using the monitor associated with the class object.
Conceptually:
Instance synchronized method
↓
instance monitor
Static synchronized method
↓
Class object's monitor
class Demo
{
static synchronized void test()
{
System.out.println(
Thread.currentThread().getName()
+ " executing"
);
}
}The lock is associated with the class object for Demo, not with individual instances.
| Synchronized Method | Synchronized Block |
|---|---|
| Locks method's associated monitor | Locks specified monitor |
| Protects the entire method | Can protect only selected code |
| Simpler | More flexible |
| May lock more code than necessary | Can minimize lock scope |
No.
Multiple threads still exist.
Synchronization means that particular protected region cannot be entered concurrently by multiple threads holding the same monitor.
Thread 1 ──→ synchronized region
↑
│
one at a time
│
Thread 2 ──→ waits
After Thread 1 releases the monitor, another eligible thread can acquire it.
If a thread attempts to enter a synchronized region whose monitor is already owned by another thread, it waits to acquire that monitor and is represented by the BLOCKED state.
Thread 1
↓
owns lock
Thread 2
↓
tries same lock
↓
BLOCKED
wait() is different from sleep().
When a thread calls:
obj.wait();while owning obj's monitor:
- It releases that monitor.
- It enters a waiting state.
- It waits for notification, interruption, or a timeout if a timed version was used.
obj.notify();wakes one thread waiting on that object's monitor.
But notification does not mean the waiting thread immediately executes.
The awakened thread must first successfully reacquire the monitor.
obj.notifyAll();makes all threads waiting on that object's monitor eligible to compete for the monitor.
Again, they do not all execute simultaneously inside the synchronized region.
Only one can own the monitor at a time.
class Shared
{
synchronized void waiting()
throws InterruptedException
{
System.out.println("Thread waiting");
wait();
System.out.println("Thread resumed");
}
synchronized void notifying()
{
System.out.println("Sending notification");
notify();
}
}
class Demo
{
public static void main(String[] args)
throws InterruptedException
{
Shared s = new Shared();
Thread t1 = new Thread(() ->
{
try
{
s.waiting();
}
catch(InterruptedException e)
{
Thread.currentThread().interrupt();
}
});
Thread t2 = new Thread(() ->
{
s.notifying();
});
t1.start();
Thread.sleep(500);
t2.start();
t1.join();
t2.join();
}
}Possible output:
Thread waiting
Sending notification
Thread resumed
This is important.
This is incorrect:
obj.wait();if the current thread does not own obj's monitor.
It results in:
IllegalMonitorStateException
Correct:
synchronized(obj)
{
obj.wait();
}For the same reason.
Correct:
synchronized(obj)
{
obj.notify();
}The calling thread must own that object's monitor.
sleep() |
wait() |
|---|---|
Thread method |
Object method |
| Used for timed suspension | Used for coordination |
| Does not release monitor | Releases corresponding monitor |
| Does not require owning a monitor | Must own corresponding monitor |
| Ends after time or interruption | Waits for notification/interruption/timeout |
| Does not transfer control through notification | Designed for monitor-based communication |
A classic synchronization problem is Producer-Consumer.
Producer
|
↓
Shared Buffer
|
↓
Consumer
Producer:
if buffer is full
↓
wait()
Consumer:
if buffer is empty
↓
wait()
When the producer adds data:
notify/notifyAll
When the consumer removes data:
notify/notifyAll
In modern Java, a BlockingQueue is often preferable to manually implementing this coordination.
Correct pattern:
synchronized(lock)
{
while(!condition)
{
lock.wait();
}
// use resource
}Not:
if(!condition)
{
lock.wait();
}The condition should be checked again after waking because the desired condition may no longer hold.
This is a very important concurrency rule.
Deadlock occurs when threads are permanently waiting for resources held by one another.
Example:
Thread 1 owns Lock A
↓
waits for Lock B
Thread 2 owns Lock B
↓
waits for Lock A
Diagram:
┌───────────────┐
↓ |
Thread 1 Lock A
| ↑
↓ |
Lock B ←────── Thread 2
Neither can proceed.
class Demo
{
static final Object lock1 = new Object();
static final Object lock2 = new Object();
public static void main(String[] args)
{
Thread t1 = new Thread(() ->
{
synchronized(lock1)
{
System.out.println("T1 got lock1");
synchronized(lock2)
{
System.out.println("T1 got lock2");
}
}
});
Thread t2 = new Thread(() ->
{
synchronized(lock2)
{
System.out.println("T2 got lock2");
synchronized(lock1)
{
System.out.println("T2 got lock1");
}
}
});
t1.start();
t2.start();
}
}Possible situation:
T1 → owns lock1 → waits for lock2
T2 → owns lock2 → waits for lock1
Program can remain stuck.
One common technique is to maintain a consistent lock ordering.
For example:
Always acquire:
Lock A
↓
Lock B
and never:
Thread 1: A → B
Thread 2: B → A
Other approaches include:
- Reducing lock scope
- Avoiding unnecessary nested locks
- Using higher-level concurrency utilities
- Using
tryLock()with timeouts where appropriate
Starvation occurs when a thread is repeatedly denied the resources or execution opportunities it needs to make progress.
Thread A → keeps getting access
Thread B → repeatedly waits
The program may still be running, but Thread B makes insufficient progress.
In livelock, threads are not blocked; they remain active but repeatedly respond to each other without making useful progress.
Thread 1 → changes action
Thread 2 → reacts
Thread 1 → changes again
Thread 2 → reacts again
They are active but accomplish nothing useful.
| Problem | Main idea |
|---|---|
| Deadlock | Threads wait for each other indefinitely |
| Starvation | A thread repeatedly fails to obtain needed resources |
| Livelock | Threads keep acting but make no useful progress |
volatile is primarily about visibility of updates between threads.
Example:
class Demo
{
volatile boolean running = true;
}If one thread changes:
running = false;another thread reading the volatile variable is guaranteed appropriate visibility according to the Java Memory Model.
No.
volatile int count;
count++;is still conceptually:
read
↓
add
↓
write
Two threads can interfere.
For compound atomic operations, consider synchronization or atomic classes.
Java provides:
AtomicInteger
AtomicLong
AtomicBoolean
Example:
import java.util.concurrent.atomic.AtomicInteger;
class Counter
{
AtomicInteger count =
new AtomicInteger(0);
void increment()
{
count.incrementAndGet();
}
}Here:
incrementAndGet()provides an atomic increment operation.
A class is thread-safe when its behavior remains correct under concurrent access according to its documented contract.
Possible techniques include:
Synchronization
Immutable state
Atomic variables
Concurrent collections
Thread confinement
Locks
Message passing
Thread confinement means ensuring that mutable data is accessed by only one thread.
If no other thread can access that mutable state concurrently, many synchronization problems disappear.
Thread 1
|
private mutable data
|
No other thread accesses it
Immutable objects are naturally easier to share between threads because their state cannot be changed after construction.
For example:
String
is immutable.
If an object cannot change, threads cannot create a race condition by modifying that object's state.
Instead of manually creating many threads:
new Thread(task1).start();
new Thread(task2).start();
new Thread(task3).start();Java provides executors.
Example:
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();
}
}The executor manages a pool of worker threads and schedules submitted tasks.
A thread pool maintains a set of reusable worker threads.
Executor
|
┌────────┼────────┐
↓ ↓ ↓
Worker Worker Worker
|
receives tasks
Benefits:
- Avoids repeatedly creating threads
- Controls concurrency
- Reuses worker threads
- Simplifies task management
Runnable task = () ->
{
System.out.println("Task");
};Does not return a result.
Callable<Integer> task = () ->
{
return 100;
};Can return a result and throw checked exceptions.
A Future represents the result of an asynchronous computation.
ExecutorService service =
Executors.newSingleThreadExecutor();
Future<Integer> future =
service.submit(() -> 100);
Integer result = future.get();
service.shutdown();get() waits if necessary for the computation to complete.
For more advanced asynchronous workflows:
CompletableFuturecan be used.
Example:
CompletableFuture
.supplyAsync(() -> 100)
.thenApply(x -> x * 2)
.thenAccept(System.out::println);Possible output:
200
It supports composition of asynchronous stages.
Ordinary collections are not automatically safe for arbitrary concurrent modification.
Java provides concurrent collections such as:
ConcurrentHashMap
CopyOnWriteArrayList
BlockingQueue
ConcurrentLinkedQueue
Example:
ConcurrentHashMap<Integer, String> map =
new ConcurrentHashMap<>();These are designed for specific concurrent-access patterns.
Java provides explicit lock classes such as:
ReentrantLock
Example:
import java.util.concurrent.locks.ReentrantLock;
class Counter
{
private int count = 0;
private final ReentrantLock lock =
new ReentrantLock();
void increment()
{
lock.lock();
try
{
count++;
}
finally
{
lock.unlock();
}
}
}To ensure the lock is released even if an exception occurs.
synchronized |
ReentrantLock |
|---|---|
| Built into Java language | Explicit lock API |
| Automatically releases monitor when leaving block | Must explicitly unlock |
| Simple | More flexible |
| No direct timed lock acquisition | Supports tryLock() |
| No explicit fairness configuration | Supports optional fairness |
| Good for many ordinary cases | Useful for advanced locking requirements |
A Semaphore controls access using a number of permits.
For example:
3 permits
↓
At most 3 threads
can access resource
simultaneously
This differs from synchronized, which provides one-at-a-time ownership of a monitor.
A CountDownLatch allows one or more threads to wait until a count reaches zero.
Conceptually:
count = 3
Task 1 → countDown()
Task 2 → countDown()
Task 3 → countDown()
count = 0
↓
waiting thread proceeds
A CyclicBarrier allows a group of threads to wait for one another at a common barrier point.
Thread 1 ──┐
Thread 2 ──┼── Barrier
Thread 3 ──┘
↓
all arrive
↓
continue
Unlike a CountDownLatch, a barrier can be reused.
ThreadLocal provides each thread with its own independent value.
Thread 1 → value A
Thread 2 → value B
Thread 3 → value C
Example:
ThreadLocal<Integer> local =
ThreadLocal.withInitial(() -> 0);
local.set(100);Each thread interacting with local gets its own associated value.
SYNCHRONIZATION
|
┌───────────────┼────────────────┐
↓ ↓ ↓
synchronized Locks Atomic
| | |
┌─────┴─────┐ ReentrantLock AtomicInteger
↓ ↓
Method Block
|
↓
Monitor
|
↓
Mutual Exclusion
|
↓
Shared Resource
|
↓
Thread Safety
INTER-THREAD COMMUNICATION
|
┌────────┼────────┐
↓ ↓ ↓
wait() notify() notifyAll()
|
↓
Object Monitor
|
↓
Shared Condition
class Counter
{
private int count = 0;
synchronized void increment()
{
count++;
}
int getCount()
{
return count;
}
}
class Demo
{
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();
System.out.println(counter.getCount());
}
}Output:
2000
Both threads share:
same Counter object
↓
same count variable
↓
synchronized increment()
↓
one thread at a time
↓
correct result
class SharedCounter
{
private int count = 0;
synchronized void increment()
{
count++;
}
int getCount()
{
return count;
}
}
class Worker extends Thread
{
private final SharedCounter counter;
Worker(
SharedCounter counter,
String name)
{
super(name);
this.counter = counter;
}
public void run()
{
for(int i = 0; i < 1000; i++)
{
counter.increment();
}
System.out.println(
getName() + " completed"
);
}
}
class Demo
{
public static void main(String[] args)
throws InterruptedException
{
SharedCounter counter =
new SharedCounter();
Worker t1 =
new Worker(counter, "Worker-1");
Worker t2 =
new Worker(counter, "Worker-2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(
"Final count = " +
counter.getCount()
);
}
}Possible output:
Worker-1 completed
Worker-2 completed
Final count = 2000
The order of the first two lines can vary.
| Method | Purpose | Releases monitor? | Associated with |
|---|---|---|---|
sleep() |
Pause current thread | ❌ No | Thread |
join() |
Wait for another thread to finish | ❌ Not generally a monitor-release mechanism | Thread |
wait() |
Coordinate using a monitor | ✅ Yes | Object |
Memorize these:
synchronized instance method
↓
locks that object
static synchronized method
↓
locks the Class object's monitor
sleep()
↓
does NOT release monitor
wait()
↓
releases corresponding monitor
wait()/notify()/notifyAll()
↓
must be called while owning corresponding monitor
volatile
↓
visibility
↓
NOT general atomicity
start()
↓
starts thread execution
run()
↓
normal method call if invoked directly
Thread object
↓
can be started only once
MULTITHREADING
|
Multiple execution paths
|
┌──────────────┼──────────────┐
↓ ↓ ↓
Thread Runnable Callable
| | |
└──────────────┼──────────────┘
↓
start()
↓
RUNNABLE
|
┌────────────────────┼─────────────────────┐
↓ ↓ ↓
BLOCKED WAITING TIMED_WAITING
| | |
└────────────────────┼─────────────────────┘
↓
RUNNABLE
↓
run() ends
↓
TERMINATED
SHARED MUTABLE DATA
|
↓
Race Condition
|
↓
Synchronization
|
┌────────────┼────────────┐
↓ ↓ ↓
synchronized Lock Atomic
|
┌─────┴─────┐
↓ ↓
Method Block
|
↓
Monitor
|
↓
Mutual Exclusion
|
↓
Thread Safety
|
↓
wait() / notify() / notifyAll()
CONCURRENCY PROBLEMS
|
┌────────────┼────────────┐
↓ ↓ ↓
Deadlock Starvation Livelock
| Concept | Remember |
|---|---|
| Process | Program in execution |
| Thread | Unit of execution within a process |
| Multithreading | Multiple threads executing concurrently |
Thread |
Class representing a thread |
Runnable |
Represents a task with run() |
Callable |
Task that can return a result |
start() |
Starts thread execution |
run() |
Thread task method; direct call is ordinary method invocation |
sleep() |
Temporarily pauses current thread |
join() |
Waits for another thread to terminate |
interrupt() |
Requests interruption |
isAlive() |
Checks whether thread is alive |
| Daemon | Background thread |
| Race condition | Result depends on unsafe timing/interleaving |
| Critical section | Code requiring coordinated access |
| Synchronization | Controls concurrent access to shared state |
| Monitor | Intrinsic lock associated with an object |
synchronized |
Mutual exclusion + memory synchronization |
wait() |
Wait and release corresponding monitor |
notify() |
Notify one waiting thread |
notifyAll() |
Notify all waiting threads |
volatile |
Visibility/ordering guarantee, not general atomicity |
| Atomic class | Provides atomic operations |
| Deadlock | Threads wait indefinitely for each other's resources |
| Starvation | Thread repeatedly fails to obtain required progress |
| Livelock | Threads remain active but make no progress |
| ExecutorService | Manages task execution using executor/thread pools |
| Future | Represents asynchronous computation result |
| CompletableFuture | Composable asynchronous computation |
| Concurrent collections | Collections designed for concurrent access |
ReentrantLock |
Explicit, flexible locking mechanism |
Semaphore |
Controls access using permits |
CountDownLatch |
Waits until count reaches zero |
CyclicBarrier |
Threads wait for one another at a barrier |
ThreadLocal |
Per-thread independent values |
start() → new thread execution
run() → ordinary method if called directly
sleep() → pause; does NOT release monitor
wait() → wait; DOES release corresponding monitor
notify() → one waiting thread becomes eligible
notifyAll() → all waiting threads become eligible
synchronized → protects shared critical sections
volatile → visibility, NOT count++ atomicity
Race condition → unsafe shared access
Deadlock → threads wait for each other
join() → current thread waits for another thread
interrupt() → request interruption, NOT forced termination
Runnable → task
Thread → execution mechanism
ExecutorService → modern task/thread management
This is the core structure you should retain before moving to the next multithreading level.