- Understanding Multithreading in Java
- Implementing Multithreading (Thread, Runnable, and Callable) in Java
- Thread Pools in Java
Understanding Multithreading in Java
Multithreading is a core concept of Java programming that allows multiple threads to run concurrently within a single process. This concurrency model enables developers to perform multiple operations simultaneously, making applications more efficient and responsive.
Key Benefits of Multithreading
- Improved Application Performance: By allowing multiple operations to run in parallel, especially on multi-core processors, multithreading can significantly enhance application performance.
- Better Resource Utilization: Multithreading enables applications to make optimal use of available CPU resources by keeping idle time to a minimum.
- Enhanced Responsiveness: User interface applications can remain responsive to user input while performing background operations on separate threads.
Implementing Multithreading (Thread, Runnable, and Callable) in Java
Java provides multiple ways to implement multithreading, including extending the Thread class and implementing the Runnable or Callable interfaces. The java.util.concurrent package further simplifies multithreading with high-level concurrency constructs.
- Thread
- A
Threadis a class that represents a thread of execution in a Java program. When you extend theThreadclass, you must override itsrun()method with the code you want the thread to execute. - Starting the thread is done by creating an instance of your
Threadsubclass and calling itsstart()method, which internally calls therun()method. - Extending the
Threadclass can be limiting because it forces your class to inherit fromThreadwhen it might actually need to inherit from another class (since Java does not support multiple inheritance).
- A
- Runnable
Runnableis an interface that you implement in a class whose instances will be executed by a thread. It has a single method calledrun().- Unlike extending
Thread, implementingRunnableallows your class to extend other classes. - To execute a
Runnable, you pass an instance of yourRunnableclass to aThreadinstance and call thestart()method on theThread. This is a more flexible approach than extendingThreadbecause it allows you to implementRunnablein classes that extend other classes.
- Callable
Callableis an interface similar toRunnablebut with some key differences. It was introduced in Java 5 to handle cases where a task needs to return a result and/or throw an exception.- The
Callableinterface has a method calledcall()which can return a value of a specified type and can throw an exception. Callabletasks need to be run by anExecutorService. When you submit aCallableto anExecutorService, you get aFutureobject back. ThisFuturecan be used to retrieve theCallable’s result, providing a way to handle the result of the asynchronous task once it completes.Callableis more versatile thanRunnablewhen it comes to exception handling and returning results.
Example of Thread
The Thread class is the fundamental building block of Java’s concurrency model. When you create a new thread, you’re creating a new concurrent path of execution within your program. You can create a thread by extending the Thread class and overriding its run method.
1
2
3
4
5
6
7
8
9
10
11
12
class MyThread extends Thread {
@Override
public void run() {
System.out.println("This is a thread running by extending Thread class.");
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start(); // Start the thread
}
}
Example of Runnable
Runnable is a functional interface in Java that represents a task to be run by a thread. Unlike extending the Thread class, implementing the Runnable interface does not create a thread. Instead, it defines a task that can be executed by any thread. The Runnable interface is a better choice when you only need to override the run method without requiring the full overhead of the Thread class.
1
2
3
4
5
6
7
8
9
10
11
12
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("This is a thread running by implementing Runnable interface.");
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start(); // Start the thread
}
}
Example of Callable
Callable is similar to Runnable but more powerful. It represents a task that returns a result and may throw an exception. Callable tasks are executed by an ExecutorService, and the result is retrieved through a Future object.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyCallable implements Callable<String> {
public String call() throws Exception {
// Perform task and return a result
return "Callable executed: " + Thread.currentThread().getName();
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<String> future = executor.submit(new MyCallable());
// Retrieves the result of the callable
String result = future.get();
System.out.println(result);
executor.shutdown();
}
}
Thread Pools in Java
Thread pools are a crucial feature of Java’s concurrency framework, managing a pool of worker threads. The thread pool reuses a fixed number of threads to execute tasks, reducing the overhead of creating and destroying threads for each task.
Advantages of Thread Pools
- Resource Management: Thread pools limit the number of simultaneous threads, preventing resource exhaustion.
- Improved Performance: Reusing existing threads reduces the latency associated with thread creation.
- Task Queuing and Execution Management: Thread pools efficiently manage a queue of tasks, executing them as threads become available.
Example of Thread Pools
Java’s ExecutorService interface provides a flexible mechanism for asynchronous task execution. The Executors class offers convenient factory methods for creating different types of thread pools.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
Runnable worker = new WorkerThread("" + i);
executor.execute(worker);
}
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");
}
}
class WorkerThread implements Runnable {
private final String command;
public WorkerThread(String command) {
this.command = command;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Start. Command = " + command);
// Task execution logic here
System.out.println(Thread.currentThread().getName() + " End.");
}
}