Multithreading in Java
Learn how Java creates and manages multiple threads for concurrent execution.
1. What is Multithreading?
Multithreading is the process of executing multiple threads concurrently within a single Java application.
A thread is a lightweight unit of execution. Multiple threads can work on different tasks while sharing the same process memory.
public class Simple {
public static void main(String[] args) {
System.out.println("Main thread is running");
Thread thread = new Thread(() -> {
System.out.println("Worker thread is running");
});
thread.start();
}
}
2. Process vs Thread
| Process | Thread |
|---|---|
| Independent program in execution | Lightweight unit inside a process |
| Has its own memory space | Shares process memory |
| More expensive to create | Less expensive to create |
| Communication is relatively expensive | Communication is easier through shared memory |
3. Creating a Thread by Extending Thread
One way to create a thread is by extending the
Thread class and overriding its run() method.
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
}
public class Simple {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
start() to create a new thread.
Calling run() directly does not create a new thread.
4. Creating a Thread Using Runnable
Implementing Runnable is generally more flexible because Java allows
a class to extend only one class.
class Task implements Runnable {
@Override
public void run() {
System.out.println("Task is running");
}
}
public class Main {
public static void main(String[] args) {
Runnable task = new Task();
Thread thread = new Thread(task);
thread.start();
}
}
5. Creating Threads Using Lambda Expressions
Since Runnable is a functional interface, it can be used with a lambda expression.
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Processing live mock test updates for CIIT students ๐ค๐จโ๐ซ...!");
});
thread.start();
}
}
6. start() vs run()
| start() | run() |
|---|---|
| Creates a new thread | Runs like a normal method |
| Execution can happen concurrently | No new thread is created |
| Preferred for starting a thread | Used when direct method execution is intended |
Thread thread = new Thread(() -> {
System.out.println("Worker");
});
thread.start(); // Creates a new thread
// thread.run(); // Executes directly on current thread
7. Thread Name and ID
Every thread has a name and an ID.
public class Program {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println(
"Name: " + Thread.currentThread().getName()
);
System.out.println(
"ID: " + Thread.currentThread().getId()
);
});
thread.setName("Worker-1");
thread.start();
}
}
8. Thread.sleep()
Thread.sleep() pauses the current thread for a specified amount of time.
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println("Start");
Thread.sleep(2000);
System.out.println("After 2 seconds");
}
}
9. join()
The join() method makes one thread wait until another thread completes.
public class Main {
public static void main(String[] args)
throws InterruptedException {
Thread worker = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("Worker: " + i);
}
});
worker.start();
worker.join();
System.out.println("Worker completed");
}
}
10. Daemon Threads
A daemon thread is a background thread that normally does not keep the JVM alive after all user threads have finished.
public class Main {
public static void main(String[] args) {
Thread daemon = new Thread(() -> {
while (true) {
System.out.println("Background task");
}
});
daemon.setDaemon(true);
daemon.start();
System.out.println("Main completed");
}
}
11. Thread Priority
Java provides thread priorities from 1 to 10.
The default priority is 5.
Thread thread = new Thread(() -> {
System.out.println("Worker");
});
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
12. Thread Lifecycle
A Java thread can move through several states:
- NEW - Thread object is created but not started.
- RUNNABLE - Thread is ready or running.
- BLOCKED - Waiting to acquire a monitor lock.
- WAITING - Waiting indefinitely for another thread.
- TIMED_WAITING - Waiting for a specified period.
- TERMINATED - Thread execution has completed.
Thread thread = new Thread(() -> {
System.out.println("Running");
});
System.out.println(thread.getState());
thread.start();
System.out.println(thread.getState());
13. Race Condition
A race condition occurs when multiple threads access shared data concurrently and the final result depends on the timing of execution.
class Counter {
int count = 0;
void increment() {
count++;
}
}
If several threads call increment() at the same time,
updates can be lost.
14. synchronized Methods
The synchronized keyword provides mutual exclusion so that
only one thread can execute the synchronized method on the same object at a time.
class Counter {
private int count = 0;
synchronized void increment() {
count++;
}
int getCount() {
return count;
}
}
15. synchronized Block
A synchronized block allows you to protect only the critical section of code.
class Counter {
private int count = 0;
void increment() {
synchronized (this) {
count++;
}
}
int getCount() {
return count;
}
}
16. Object Locks
Every Java object can act as a monitor lock. Synchronized instance methods use the object's monitor.
class BankAccount {
private double balance = 1000;
synchronized void withdraw(double amount) {
if (balance >= amount) {
balance -= amount;
}
}
synchronized double getBalance() {
return balance;
}
}
17. ReentrantLock
ReentrantLock provides more explicit locking control than
the synchronized keyword.
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();
}
}
}
finally block.
18. ReadWriteLock
ReadWriteLock allows multiple readers at the same time while
keeping writes exclusive.
import java.util.concurrent.locks.*;
class DataStore {
private final ReadWriteLock lock =
new ReentrantReadWriteLock();
private String data = "CIIT";
String read() {
lock.readLock().lock();
try {
return data;
}
finally {
lock.readLock().unlock();
}
}
void write(String value) {
lock.writeLock().lock();
try {
data = value;
}
finally {
lock.writeLock().unlock();
}
}
}
19. volatile Keyword
The volatile keyword provides visibility guarantees for a variable
between threads.
class Worker {
private volatile boolean running = true;
void stop() {
running = false;
}
void work() {
while (running) {
// perform work
}
}
}
count++ atomic.
20. Atomic Classes
Atomic classes provide thread-safe operations without manually using synchronized blocks for many common counters and state variables.
import java.util.concurrent.atomic.AtomicInteger;
class Counter {
private final AtomicInteger count =
new AtomicInteger(0);
void increment() {
count.incrementAndGet();
}
int getCount() {
return count.get();
}
}
21. ExecutorService
ExecutorService manages a pool of worker threads and is usually
preferred over manually creating many threads.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Program {
public static void main(String[] args) {
ExecutorService executor =
Executors.newFixedThreadPool(3);
executor.submit(() -> {
System.out.println("CIIT Server: Generating student report cards โน๏ธ.");
});
executor.submit(() -> {
System.out.println("CIIT Server: Sending automated attendance SMS to parents ๐โ๏ธ.");
});
executor.shutdown();
}
}
22. Callable
Callable is similar to Runnable, but it can return a result
and throw checked exceptions.
import java.util.concurrent.*;
public class Main {
public static void main(String[] args)
throws Exception {
ExecutorService executor =
Executors.newSingleThreadExecutor();
Callable<Integer> task = () -> {
return 10 + 20;
};
Future<Integer> result =
executor.submit(task);
System.out.println(result.get());
executor.shutdown();
}
}
23. Future
A Future represents the result of an asynchronous computation.
ExecutorService executor =
Executors.newSingleThreadExecutor();
Future<String> future =
executor.submit(() -> {
Thread.sleep(1000);
return "Task completed";
});
System.out.println(future.get());
executor.shutdown();
24. CountDownLatch
CountDownLatch allows one or more threads to wait until a set of
operations has completed.
import java.util.concurrent.CountDownLatch;
CountDownLatch latch =
new CountDownLatch(2);
Thread t1 = new Thread(() -> {
System.out.println("CIIT Server 1: Student Records Database initialized ๐จโ๐ซ๐ค .");
latch.countDown();
});
Thread t2 = new Thread(() -> {
System.out.println("CIIT Server 2: Fee Payment Portal initialized ๐โ๏ธ.");
latch.countDown();
});
t1.start();
t2.start();
latch.await();
System.out.println("All CIIT core systems ready. Opening Main Admission Portal ๐ค๐จโ๐ซ!");
25. Semaphore
A Semaphore controls access to a limited number of resources.
import java.util.concurrent.Semaphore;
Semaphore semaphore =
new Semaphore(2);
semaphore.acquire();
try {
System.out.println("Using resource");
}
finally {
semaphore.release();
}
26. Deadlock
A deadlock occurs when two or more threads wait forever for resources held by each other.
Thread 1:
Lock A โ waits for Lock B
Thread 2:
Lock B โ waits for Lock A
27. Starvation
Starvation occurs when a thread continuously fails to obtain the CPU or required resources because other threads keep getting priority.
28. Livelock
In a livelock, threads are active but continuously respond to each other without making useful progress.
29. Concurrent Collections
Java provides thread-safe collections designed for concurrent applications.
import java.util.concurrent.ConcurrentHashMap;
ConcurrentHashMap<String, Integer> map =
new ConcurrentHashMap<>();
map.put("Java", 100);
map.put("Spring", 200);
System.out.println(map.get("Java"));
Common concurrent collections include:
ConcurrentHashMapCopyOnWriteArrayListBlockingQueueConcurrentLinkedQueue
30. Immutability and Thread Safety
Immutable objects cannot change after creation. They are naturally safer to share between multiple threads.
public final class Employee {
private final String name;
public Employee(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
31. Virtual Threads
Modern Java provides virtual threads, which are lightweight threads designed to make high-concurrency applications easier to build.
public class Main {
public static void main(String[] args) {
Thread.startVirtualThread(() -> {
System.out.println("
CIIT high-concurrency student portal request running on an ultra-lightweight virtual thread ๐ค๐."
);
});
}
}
32. Example ๐๐ค
Imagine an online shopping application processing multiple orders. Different tasks can execute concurrently:
- Validate payment
- Check inventory
- Send confirmation email
- Update order status
- Generate notification
ExecutorService executor =
Executors.newFixedThreadPool(4);
executor.submit(() ->
System.out.println("CIIT System: Processing admission fee payment confirmation โน๏ธ.");
);
executor.submit(() ->
System.out.println("CIIT System: Checking batch seat availability and updates ๐.");
);
executor.submit(() ->
System.out.println("CIIT System: Sending welcome kit email with student portal login info ๐.");
);
executor.submit(() ->
System.out.println("CIIT System: Allocating roll number and updating student roster status ๐คทโ.");
);
executor.shutdown();
33. Multithreading Best Practices
- Prefer
ExecutorServicefor managing thread pools. - Keep shared mutable state to a minimum.
- Prefer immutable objects where possible.
- Keep synchronized sections small.
- Always release explicit locks.
- Avoid unnecessary thread creation.
- Use concurrent collections when appropriate.
- Do not depend on thread priority for program correctness.
- Use virtual threads for suitable high-concurrency workloads.
- Design carefully to avoid deadlocks.
34. Interview Questions
start() creates a new thread and eventually invokes
run(). Calling run() directly executes
the method on the current thread.
Summary
In this lesson, you learned:
- What multithreading is
- Process vs thread
- Creating threads with Thread and Runnable
- Lambda-based threads
- start(), run(), sleep(), and join()
- Thread lifecycle and priorities
- Race conditions and synchronization
- Locks and atomic classes
- ExecutorService, Callable, and Future
- CountDownLatch and Semaphore
- Deadlock, starvation, and livelock
- Concurrent collections
- Immutability and thread safety
- Modern virtual threads