Introduction
Multithreading in Java allows concurrent execution of two or more parts of a program to maximize CPU utilization. Each part of such a program is called a thread. Java provides built-in support for multithreaded programming through its Thread
class and the Runnable
interface.
Table of Contents
- Basics of Multithreading
- Creating Threads
- Extending the
Thread
Class - Implementing the
Runnable
Interface
- Extending the
- Thread Lifecycle
- Thread Methods
- Synchronization
- Inter-Thread Communication
- Thread Pools
- Example Programs
- Conclusion
1. Basics of Multithreading
Multithreading allows multiple threads to run concurrently. Each thread runs a separate path of execution. Java's built-in Thread
class and Runnable
interface make it easy to implement multithreading.
Benefits of Multithreading:
- Improved performance and responsiveness.
- Efficient utilization of CPU resources.
- Simplified modeling of real-world systems.
2. Creating Threads
Extending the Thread
Class
To create a thread by extending the Thread
class, you need to:
- Create a new class that extends
Thread
. - Override the
run
method with the code you want the thread to execute. - Create an instance of the new class and call the
start
method.
Example:
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " is running. Count: " + i);
try {
Thread.sleep(1000); // Simulate some work with sleep
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
}
}
Implementing the Runnable
Interface
To create a thread by implementing the Runnable
interface, you need to:
- Create a new class that implements
Runnable
. - Override the
run
method with the code you want the thread to execute. - Create an instance of
Thread
class, passing theRunnable
object to theThread
constructor. - Call the
start
method.
Example:
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " is running. Count: " + i);
try {
Thread.sleep(1000); // Simulate some work with sleep
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class RunnableExample {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
thread1.start();
thread2.start();
}
}
3. Thread Lifecycle
A thread in Java goes through various states in its lifecycle:
- New: A thread that has been created but not yet started.
- Runnable: A thread that is ready to run and waiting for CPU time.
- Running: A thread that is executing.
- Blocked: A thread that is blocked waiting for a monitor lock.
- Waiting: A thread that is waiting indefinitely for another thread to perform a particular action.
- Timed Waiting: A thread that is waiting for another thread to perform a particular action within a stipulated amount of time.
- Terminated: A thread that has exited.
4. Thread Methods
start()
Starts the execution of the thread.
Example:
thread.start();
run()
If a thread was constructed using a separate Runnable
object, then that Runnable
object's run
method is called; otherwise, this method does nothing and returns.
Example:
public void run() {
// code to be executed
}
sleep(long millis)
Puts the currently executing thread to sleep for the specified number of milliseconds.
Example:
Thread.sleep(1000); // Sleeps for 1 second
interrupt()
Interrupts a thread that is in the sleeping, waiting, or blocked state.
Example:
thread.interrupt();
join()
Waits for the thread to die.
Example:
thread.join();
isAlive()
Tests if the thread is alive.
Example:
boolean alive = thread.isAlive();
getName()
and setName(String name)
Gets or sets the name of the thread.
Example:
String name = thread.getName();
thread.setName("New Thread Name");
getPriority()
and setPriority(int priority)
Gets or sets the priority of the thread.
Example:
int priority = thread.getPriority();
thread.setPriority(Thread.MAX_PRIORITY);
getState()
Returns the state of the thread.
Example:
Thread.State state = thread.getState();
5. Synchronization
Synchronization is used to control the access of multiple threads to shared resources. It prevents data inconsistency and race conditions.
Synchronized Methods
A synchronized method ensures that only one thread can execute it at a time for a particular object.
Example:
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SynchronizedExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count: " + counter.getCount());
}
}
Synchronized Blocks
A synchronized block is a block of code within a method that is synchronized on a specified object, providing more granular control over the synchronization.
Example:
class Counter {
private int count = 0;
public void increment() {
synchronized (this) {
count++;
}
}
public int getCount() {
return count;
}
}
public class SynchronizedBlockExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count: " + counter.getCount());
}
}
6. Inter-Thread Communication
Inter-thread communication in Java is achieved using wait()
, notify()
, and notifyAll()
methods. These methods are used to synchronize the activities of multiple threads.
Example:
class SharedResource {
private int data = 0;
private boolean available = false;
public synchronized void produce(int value) throws InterruptedException {
while (available) {
wait();
}
data = value;
available = true;
notify();
}
public synchronized int consume() throws InterruptedException {
while (!available) {
wait();
}
available = false;
notify();
return data;
}
}
public class InterThreadCommunicationExample {
public static void main(String[] args) {
SharedResource sharedResource = new SharedResource();
Thread producer = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
sharedResource.produce(i);
System.out.println("Produced: " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread consumer = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
int value = sharedResource.consume();
System.out.println("Consumed: " + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
consumer.start();
}
}
7. Thread Pools
Thread pooling is a technique to reuse a fixed number of threads to execute tasks, which improves the performance of a multithreaded application.
Using ExecutorService
Example:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolingExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 5; i++) {
final int taskId = i;
executorService.submit(() -> {
System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());
try {
Thread.sleep(2000); // Simulate a long-running task
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
Using Executors
Factory Methods
newFixedThreadPool(int nThreads)
: Creates a thread pool with a fixed number of threads.newCachedThreadPool()
: Creates a thread pool that creates new threads as needed but will reuse previously constructed threads when they are available.newSingleThreadExecutor()
: Creates an executor that uses a single worker thread.newScheduledThreadPool(int corePoolSize)
: Creates a thread pool that can schedule commands to run after a given delay or to execute periodically.
8. Example Programs
Example 1: Creating Multiple Threads
Example:
class MyRunnable implements Runnable {
private final int threadNumber;
public MyRunnable(int threadNumber) {
this.threadNumber = threadNumber;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("Thread " + threadNumber + " is running. Count: " + i);
try {
Thread.sleep(1000); // Simulate some work with sleep
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class MultiThreadExample {
public static void main(String[] args) {
for (int i = 1; i <= 3; i++) {
Thread thread = new Thread(new MyRunnable(i));
thread.start();
}
}
}
Example 2: Synchronizing Threads
Example:
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SynchronizationExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count: " + counter.getCount());
}
}
Example 3: Inter-Thread Communication
Example:
class SharedResource {
private int data = 0;
private boolean available = false;
public synchronized void produce(int value) throws InterruptedException {
while (available) {
wait();
}
data = value;
available = true;
notify();
}
public synchronized int consume() throws InterruptedException {
while (!available) {
wait();
}
available = false;
notify();
return data;
}
}
public class InterThreadCommunicationExample {
public static void main(String[] args) {
SharedResource sharedResource = new SharedResource();
Thread producer = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
sharedResource.produce(i);
System.out.println("Produced: " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread consumer = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
int value = sharedResource.consume();
System.out.println("Consumed: " + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
consumer.start();
}
}
9. Conclusion
Multithreading in Java is a powerful feature that allows for concurrent execution of tasks, improving performance and responsiveness of applications. By understanding the basics of thread creation, lifecycle, synchronization, inter-thread communication, and thread pooling, you can effectively utilize multithreading in your Java programs. The examples provided demonstrate various aspects of multithreading and serve as a foundation for more advanced multithreaded applications.
Happy coding!
Comments
Post a Comment
Leave Comment