Multithreading refers to the ability of a program to execute multiple threads (sequences of instructions) concurrently. Java provides several ways to create threads and execute them concurrently.
Threads vs Processes
Threads share the same memory and resources while processes have separate memory and resources.
Creating new processes is expensive but creating threads is cheap.
Creating Threads
There are two ways to create threads in Java:
By extending the Thread class
By implementing the Runnable interface
Extending Thread Class:
public class MyThread extends Thread {
public void run() {
// Thread execution code
}
}
MyThread t = new MyThread();
t.start(); // Starts the thread execution
Implementing Runnable:
class MyRunnable implements Runnable {
public void run() {
// Thread execution code
}
}
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start(); // Starts the thread execution
Advantages of using Runnable over extending Thread:
It follows OOP principles (favor composition over inheritance)
A class can implement multiple interfaces but extend only one class
Thread Life Cycle
A thread goes through the following states:
New
Runnable
Running
Blocked
Waiting
Terminated
Available methods on Thread class:
start() - Starts the thread's execution
run() - Contains the code representing the thread's activity
yield() - Pauses the currently running thread
join() - Waits for a thread to terminate
sleep(millis) - Pauses the current thread for the given time
getName() - Returns the name of this thread
setName() - Sets the name of this thread
Joining Threads
The join() method waits for a thread to terminate:
t.join(); // Waits for thread t to terminate
A Thread can join itself:
public void run() {
// ...
join(); // Waits for this thread to terminate
}
Thread Synchronization
When multiple threads access shared data concurrently, some form of synchronization is required to avoid race conditions and inconsistent data.
Synchronized Methods:
public synchronized void method() {
// Only one thread can execute
// this method at a time
}
Synchronized Blocks:
public void method() {
synchronized(this) {
// Only one thread can execute
// this synchronized block
// at a time
}
}
Here, this
refers to the current object and acts as the lock.
Thread Pool
A thread pool contains pre-instantiated threads that are used to execute submitted tasks. It avoids the overhead of creating new threads for each task.
Java provides the ExecutorService interface to implement the thread pool:
ExecutorService pool = Executors.newFixedThreadPool(5);
pool.submit(new RunnableTask());
pool.shutdown();
This creates a thread pool with 5 threads and submits a task to it. Finally, we shut down the pool when tasks are complete.