Main Memory
┌─────────────────────────────────────────┐
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ │ Thread 1│ │ Thread 2│ │ Thread 3│
│ └─────────┘ └─────────┘ └─────────┘
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Shared Objects │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────┘
Thread Lifecycle Diagram
┌──────┐ start() ┌──────────┐
│ New │─────────────────────▶ Runnable ◀───┐
└──────┘ └──────────┘ │
│ │
│ │ notify()
│ │ notifyAll()
▼ │ timeout
┌──────────────┐ block for I/O │ │
│ Blocked │◀─────────────────┤ │
└──────────────┘ │ │
│ │
┌──────────────┐ wait() │ │
│ Waiting │◀─────────────────┘ │
└──────────────┘ │
│ │
│ sleep(time) │
│ wait(time) │
▼ │
┌──────────────┐ │
│ Timed Waiting│─────────────────────────────┘
└──────────────┘
│
│ run() completes
│ stop()
▼
┌──────────────┐
│ Terminated │
└──────────────┘
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running: " + Thread.currentThread().getName());
// Code to be executed in this thread
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(1000); // Pause for 1 second
} catch (InterruptedException e) {
System.out.println("Thread interrupted");
}
}
}
public static void main(String[] args) {
MyThread thread1 = new MyThread();
thread1.setName("MyThread-1");
thread1.start(); // Start the thread
MyThread thread2 = new MyThread();
thread2.setName("MyThread-2");
thread2.start(); // Start another thread
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running: " + Thread.currentThread().getName());
// Code to be executed in this thread
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(1000); // Pause for 1 second
} catch (InterruptedException e) {
System.out.println("Thread interrupted");
}
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
thread1.setName("RunnableThread-1");
thread1.start(); // Start the thread
Thread thread2 = new Thread(myRunnable);
thread2.setName("RunnableThread-2");
thread2.start(); // Start another thread
}
}
public class AnonymousThreadExample {
public static void main(String[] args) {
// Using anonymous class extending Thread
Thread thread1 = new Thread() {
@Override
public void run() {
System.out.println("Anonymous Thread is running");
// Thread code here
}
};
thread1.start();
// Using anonymous class implementing Runnable
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Anonymous Runnable is running");
// Thread code here
}
};
Thread thread2 = new Thread(runnable);
thread2.start();
}
}
public class LambdaThreadExample {
public static void main(String[] args) {
// Using lambda expression
Thread thread = new Thread(() -> {
System.out.println("Lambda Thread is running");
for (int i = 0; i < 5; i++) {
System.out.println("Lambda Thread: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorExample {
public static void main(String[] args) {
// Create a fixed thread pool with 3 threads
ExecutorService executor = Executors.newFixedThreadPool(3);
// Submit tasks to the executor
for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " is running on " +
Thread.currentThread().getName());
// Task code here
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// Shutdown the executor when done
executor.shutdown();
}
}
thread.start(); // Starts the thread by calling its run() method
public class JoinExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println("Thread 1 started");
try {
Thread.sleep(3000); // Simulate work
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1 completed");
});
Thread thread2 = new Thread(() -> {
System.out.println("Thread 2 started");
try {
thread1.join(); // Wait for thread1 to complete
System.out.println("Thread 1 joined, now Thread 2 continues");
Thread.sleep(2000); // More work
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2 completed");
});
thread1.start();
thread2.start();
}
}
try {
Thread.sleep(1000); // Sleep for 1 second
} catch (InterruptedException e) {
// Handle interruption
}
public class InterruptExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
System.out.println("Working... " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Thread was interrupted!");
return; // Exit the thread
}
System.out.println("Thread completed normally");
});
thread.start();
// Let the thread run for a while
try {
Thread.sleep(3500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Interrupt the thread
thread.interrupt();
}
}
thread.setPriority(Thread.MAX_PRIORITY); // 10
thread.setPriority(Thread.NORM_PRIORITY); // 5 (default)
thread.setPriority(Thread.MIN_PRIORITY); // 1
thread.setName("WorkerThread-1");
String threadName = thread.getName();
Thread.State state = thread.getState(); // Returns one of the Thread.State enum values
public class Counter {
private int count = 0;
// Synchronized method
public synchronized void increment() {
count++;
}
// Synchronized block
public void incrementWithBlock() {
synchronized(this) {
count++;
}
}
public int getCount() {
return count;
}
}
Thread Synchronization Diagram
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Thread 1 │ │ Thread 2 │ │ Thread 3 │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────┐
│ │
│ ┌─────────────────────┐ │
│ │ Synchronized Block │ │
│ │ │ │
│ │ Only one thread │ │
│ │ can enter at a │ │
│ │ time │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // Acquire the lock
try {
count++;
} finally {
lock.unlock(); // Always release the lock in a finally block
}
}
public int getCount() {
return count;
}
}
public class VolatileExample {
private volatile boolean flag = false;
public void setFlag() {
flag = true; // This change is immediately visible to all threads
}
public boolean isFlag() {
return flag;
}
}
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // Atomic operation
}
public int getCount() {
return count.get();
}
}
public class MessageQueue {
private String message;
private boolean empty = true;
public synchronized String receive() {
while (empty) {
try {
wait(); // Release lock and wait for a notification
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
empty = true;
notifyAll(); // Notify waiting threads
return message;
}
public synchronized void send(String message) {
while (!empty) {
try {
wait(); // Wait until the queue is empty
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
empty = false;
this.message = message;
notifyAll(); // Notify waiting threads
}
}
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionExample {
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final String[] buffer = new String[10];
private int count = 0, putIndex = 0, takeIndex = 0;
public void put(String item) throws InterruptedException {
lock.lock();
try {
while (count == buffer.length) {
notFull.await(); // Wait until buffer is not full
}
buffer[putIndex] = item;
putIndex = (putIndex + 1) % buffer.length;
count++;
notEmpty.signal(); // Signal that buffer is not empty
} finally {
lock.unlock();
}
}
public String take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // Wait until buffer is not empty
}
String item = buffer[takeIndex];
takeIndex = (takeIndex + 1) % buffer.length;
count--;
notFull.signal(); // Signal that buffer is not full
return item;
} finally {
lock.unlock();
}
}
}
public class ProducerConsumerExample {
private static final int BUFFER_SIZE = 5;
private final Queue<Integer> buffer = new LinkedList<>();
private final Object lock = new Object();
class Producer implements Runnable {
@Override
public void run() {
int value = 0;
while (true) {
synchronized (lock) {
while (buffer.size() == BUFFER_SIZE) {
try {
lock.wait(); // Buffer is full, wait
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
System.out.println("Producing: " + value);
buffer.add(value++);
lock.notifyAll(); // Notify consumers
}
// Simulate some work
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
while (true) {
synchronized (lock) {
while (buffer.isEmpty()) {
try {
lock.wait(); // Buffer is empty, wait
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
int value = buffer.poll();
System.out.println("Consuming: " + value);
lock.notifyAll(); // Notify producers
}
// Simulate some work
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
}
public void start() {
Thread producerThread = new Thread(new Producer());
Thread consumerThread = new Thread(new Consumer());
producerThread.start();
consumerThread.start();
}
public static void main(String[] args) {
new ProducerConsumerExample().start();
}
}
Producer-Consumer Diagram
┌───────────────┐ ┌───────────────┐
│ Producer │ │ Consumer │
└───────┬───────┘ └───────┬───────┘
│ │
│ produces │ consumes
▼ ▼
┌───────────────────────────────────────────────┐
│ │
│ Shared Buffer │
│ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │
│ │ 1 │ 2 │ 3 │ 4 │ 5 │ │ │ │
│ └─────┴─────┴─────┴─────┴─────┴─────┴─────┘ │
│ │
└───────────────────────────────────────────────┘
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class BlockingQueueExample {
private static final int BUFFER_SIZE = 5;
private final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(BUFFER_SIZE);
class Producer implements Runnable {
@Override
public void run() {
int value = 0;
while (true) {
try {
System.out.println("Producing: " + value);
queue.put(value++); // Blocks if queue is full
Thread.sleep(1000); // Simulate work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
while (true) {
try {
int value = queue.take(); // Blocks if queue is empty
System.out.println("Consuming: " + value);
Thread.sleep(2000); // Simulate work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
}
public void start() {
Thread producerThread = new Thread(new Producer());
Thread consumerThread = new Thread(new Consumer());
producerThread.start();
consumerThread.start();
}
public static void main(String[] args) {
new BlockingQueueExample().start();
}
}
public class DaemonThreadExample {
public static void main(String[] args) {
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("Daemon thread is running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// Set as daemon thread
daemonThread.setDaemon(true);
daemonThread.start();
// Main thread sleeps for 5 seconds
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread exiting, daemon thread will be terminated");
// When main thread exits, the JVM will terminate, and the daemon thread will be terminated
}
}
Daemon Thread vs User Thread
┌───────────────────────────────────────────────────┐
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ User Thread │ │ Daemon Thread │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ - Prevents JVM exit - Does not prevent │
│ when running JVM exit │
│ │
│ - Must complete - Terminated when │
│ for program to end all user threads end │
│ │
│ - Default thread type - Must be explicitly │
│ set as daemon │
│ │
└───────────────────────────────────────────────────┘
ExecutorService executor = Executors.newFixedThreadPool(5);
ExecutorService executor = Executors.newCachedThreadPool();
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorExample {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
// Execute a task after 2 seconds delay
executor.schedule(() -> {
System.out.println("Delayed task executed");
}, 2, TimeUnit.SECONDS);
// Execute a task every 3 seconds, starting after 0 seconds
executor.scheduleAtFixedRate(() -> {
System.out.println("Periodic task executed at fixed rate");
}, 0, 3, TimeUnit.SECONDS);
// Execute a task every 3 seconds after the previous task completes
executor.scheduleWithFixedDelay(() -> {
System.out.println("Periodic task executed with fixed delay");
try {
Thread.sleep(1000); // Simulate work
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 0, 3, TimeUnit.SECONDS);
// Let the tasks run for a while
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Shutdown the executor
executor.shutdown();
}
}
ExecutorService executor = Executors.newSingleThreadExecutor();
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorSubmitExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
// Submit a Runnable task
executor.submit(() -> {
System.out.println("Runnable task executed by " + Thread.currentThread().getName());
});
// Submit a Callable task that returns a result
Future<String> future = executor.submit(() -> {
System.out.println("Callable task executed by " + Thread.currentThread().getName());
return "Task Result";
});
try {
// Get the result of the Callable task
String result = future.get();
System.out.println("Task result: " + result);
} catch (Exception e) {
e.printStackTrace();
}
// Shutdown the executor
executor.shutdown();
}
}
executor.shutdown(); // Allows previously submitted tasks to execute before terminating
// or
executor.shutdownNow(); // Attempts to stop all actively executing tasks and returns a list of tasks that were awaiting execution
public class ThreadLocalExample {
// ThreadLocal variable
private static final ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
// Create two threads
Thread thread1 = new Thread(() -> {
// Set thread-local value for this thread
threadLocalValue.set(1);
System.out.println("Thread 1: " + threadLocalValue.get());
// Sleep to demonstrate that the value persists
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Value is still 1 for this thread
System.out.println("Thread 1 (after sleep): " + threadLocalValue.get());
});
Thread thread2 = new Thread(() -> {
// Sleep to ensure thread1 sets its value first
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// This thread has its own copy of the variable, initialized to 0
System.out.println("Thread 2 (initial): " + threadLocalValue.get());
// Set a different value for this thread
threadLocalValue.set(2);
System.out.println("Thread 2 (after set): " + threadLocalValue.get());
});
thread1.start();
thread2.start();
}
}
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// Multiple threads can safely modify the map
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
map.put("Key-" + i, i);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 1000; i < 2000; i++) {
map.put("Key-" + i, i);
}
});
thread1.start();
thread2.start();