Java-Threads
A thread is a lightweight sub-process that runs independently within a program.
Java supports multithreading to allow concurrent execution.
Daemon threads run in the background (e.g., garbage collection). They don’t prevent the JVM from exiting.
Thread Class:class MyThread extends Thread {
public void run() {
System.out.println("Thread running");
}
}
new MyThread().start();
Runnable:class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable running");
}
}
new Thread(new MyRunnable()).start();
Using Callable and Future:Callable<String> task = () -> "Callable result";
Future<String> future = Executors.newSingleThreadExecutor().submit(task);
System.out.println(future.get());
Thread States
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
Wait/Notify MechanismUsed for communication between threads (especially for producer-consumer)synchronized(obj) {
obj.wait(); // Waits
obj.notify(); // Wakes up one thread
obj.notifyAll();// Wakes up all waiting threads
}
ConcurrentModificationException & Fail-Fast vs Fail-Safe
Happens when a collection is modified while iterating.
- Fail-Fast (e.g., ArrayList) throws exception.
- Fail-Safe (e.g., CopyOnWriteArrayList) allows modification without error by working on cloned data.
Volatile, Synchronized, and Atomic Variablesvolatile: ensures visibility across threads but doesn’t guarantee atomicity.synchronized: locks access to code blocks or methods.- Object-level lock:
synchronized(this) - Class-level lock:
synchronized(MyClass.class)
- Atomic classes (e.g.,
AtomicInteger) provide lock-free thread safety.
Making Collections Thread-SafeUse Collections.synchronizedList() or thread-safe classes like:
ConcurrentHashMap
CopyOnWriteArrayList
BlockingQueue
Locks in JavaReentrantLockLock lock = new ReentrantLock();
lock.lock();
try {
// critical section
} finally {
lock.unlock();
}
Other Locks:
ReadWriteLock
StampedLock
ReentrantReadWriteLock
ThreadLocal and InheritableThreadLocalThreadLocal: provides isolated variables per thread.
InheritableThreadLocal: child threads inherit values from parent.
ExecutorService & Thread PoolsThread Pool Description newFixedThreadPool(n)Reuses a fixed number of threads newCachedThreadPool() Creates new threads as needed newSingleThreadExecutor()Executes tasks sequentially newScheduledThreadPool(n)Executes tasks after delay
Example:ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(() -> System.out.println("Running task"));
pool.shutdown();
Producer-Consumer ProblemTraditional Way Using wait and notify:class SharedQueue {
Queue<Integer> queue = new LinkedList<>();
// synchronized methods with wait/notify
}
Using BlockingQueue:BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
queue.put(1); // producer
queue.take(); // consumer
CompletableFutureCompletableFuture is part of the java.util.concurrent package. It represents a future result of an asynchronous computation and allows you to chain multiple tasks together.CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("Running async task");
});
future.join(); // Wait for completion
With Result:CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "Hello from CompletableFuture!";
});
System.out.println(future.join()); // Prints result
Chaining Tasks:CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> "Step 1")
.thenApply(step -> step + " -> Step 2")
.thenApply(step -> step + " -> Step 3");
System.out.println(future.join());
// Output: Step 1 -> Step 2 -> Step 3
Handling Exceptions:CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> {
if (true) throw new RuntimeException("Oops!");
return "Success";
})
.exceptionally(ex -> "Handled error: " + ex.getMessage());
System.out.println(future.join());
Combining Multiple Futures:CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combined = future1.thenCombine(future2, (a, b) -> a + " " + b);
System.out.println(combined.join()); // Output: Hello World
CountDownLatchUsed to delay thread execution until other threads have completed.CountDownLatch latch = new CountDownLatch(3);
Runnable task = () -> {
System.out.println("Task done");
latch.countDown(); // Reduces the count
};
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
latch.await(); // Waits until count reaches zero
System.out.println("All tasks completed");
CyclicBarrierSynchronizes threads to wait for each other at a common barrier point.CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("All threads reached the barrier");
});
Runnable task = () -> {
System.out.println("Task reached barrier");
try {
barrier.await(); // Waits for others
} catch (Exception e) {
e.printStackTrace();
}
};
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
Java Memory Model (JMM)Defines how threads interact through memory.
Ensures visibility and ordering of shared variables.
Addresses issues like instruction reordering or stale values.
Thread Dumps and AnalysisThread dumps are vital for diagnosing issues like deadlocks, performance bottlenecks, or stuck threads.Take a Thread Dump:On Unix/Linux/macOS: kill -3 <pid>
On Windows (using jstack): jstack <pid> > threadDump.txt
Inside IDEs: Many Java IDEs like IntelliJ and Eclipse offer built-in thread dump tools.
Analyze a Thread Dump:- Look for threads stuck in BLOCKED or WAITING state.
- Check stack traces for nested locks or unending loops.
- Identify thread names, thread IDs, priority levels, and locks held.
- Use visualization tools like:
- FastThread.io
- JMC (Java Mission Control)
- IntelliJ’s diagnostic tools
Virtual Threads:Virtual threads are lightweight threads managed by the Java Virtual Machine (JVM) rather than the operating system. Unlike traditional platform threads, they don’t tie up OS resources when blocked (e.g., waiting for I/O). This means you can spawn millions of virtual threads without overwhelming the system.
They’re ideal for tasks that spend most of their time waiting—like database queries, HTTP calls, or file I/O.
They don’t speed up execution but allow greater concurrency with simpler code.
How to Create Virtual Threads:
Using Thread.ofVirtual()Thread thread = Thread.ofVirtual().start(() -> {
System.out.println("Running in a virtual thread!");
});
thread.join(); // Wait for completion
Using Executors.newVirtualThreadPerTaskExecutor()ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
executor.submit(() -> {
System.out.println("Hello from virtual thread pool!");
});
executor.shutdown();
Using Thread.Builder APIThreadFactory factory = Thread.builder().virtual().factory();
Thread thread = factory.newThread(() -> {
System.out.println("Built with Thread.Builder");
});
thread.start();
When Should we Use Virtual Threads?Use virtual threads when:
You have high-throughput applications (e.g., web servers) handling thousands of concurrent tasks.
Your tasks are I/O-bound, like calling APIs, reading files, or querying databases.
You want to avoid the complexity of asynchronous programming (e.g., CompletableFuture, reactive streams).
Avoid virtual threads for:
CPU-intensive tasks (e.g., image processing, data crunching).
Code that heavily uses synchronized blocks or native methods, which can pin the underlying carrier thread.
Create a Thread That Prints Numbers from 1 to 100
Use both
ThreadandRunnableimplementations.
Implement a Simple Counter with Multiple Threads
Launch 5 threads that increment a shared counter. Ensure correct synchronization.
Producer-Consumer Problem Using
wait()andnotify()Use a shared
Queueand synchronized methods.
Solve Producer-Consumer Using
BlockingQueueImplement producers and consumers with proper thread coordination.
Print Even and Odd Numbers with Two Threads
One thread prints even numbers, another prints odd. Use synchronization to alternate correctly.
Create a Task Execution Using
ExecutorServiceSubmit tasks and collect results using
CallableandFuture.
Implement a ThreadPool That Processes File Names in Parallel
Given a list of file names, read contents concurrently and print the first line from each.
Use
CompletableFutureto Run Multiple Tasks in ParallelCombine results from two services and log a final output.
Demonstrate ThreadLocal Usage
Simulate an API call that stores user-specific context in a thread-local variable.
Simulate Bank Transactions from Multiple Threads
Ensure account balance updates are thread-safe using
synchronizedorReentrantLock.
Build a Countdown Timer with
CountDownLatch
Use 3 threads to perform different tasks and wait for all to finish before proceeding.
Synchronize Data Writing and Reading Using
CyclicBarrier
Synchronize multiple writer threads that trigger a reader once all complete.
Handle Shared Access Using Atomic Variables
Replace
synchronizedblock withAtomicIntegerfor a shared counter.
Compare Performance Between
synchronized,ReentrantLock, andStampedLock
Benchmark with 1000 threads incrementing a counter.
Simulate a Deadlock
Purposefully create a deadlock and then analyze with thread dumps.