Creating Threads
About
In Java, threads can be created and managed using multiple approaches. Each approach has its use cases and trade-offs. Some of the approaches are given below.
1. Extending the Thread
Class
Thread
ClassThe simplest way to create a thread is by extending the
Thread
class.The
run()
method is overridden to define the thread's behavior.A new thread is started using the
start()
method.
Example:
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Thread is running...");
}
}
public class SomeMain {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // Start the thread
// Thread-0 Thread is running...
}
}
Limitations:
Java does not support multiple inheritance, so extending
Thread
prevents extending other classes.Better to use the
Runnable
interface if we need more flexibility.
2. Implementing the Runnable
Interface
Runnable
InterfaceInstead of extending
Thread
, theRunnable
interface can be implemented.The
run()
method is implemented inside the class.The thread is started using
Thread
class.
Example:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Thread is running...");
}
}
public class RunnableExample {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
// Thread-0 Thread is running...
}
}
Advantages:
Allows the class to extend other classes (unlike extending
Thread
).Encouraged in multi-threaded environments where tasks can be separated from threads.
3. Using Callable
and Future
(Return Value from Thread)
Callable
and Future
(Return Value from Thread)The
Callable<T>
interface (fromjava.util.concurrent
) allows a thread to return a result.The
Future<T>
interface is used to retrieve the result of the thread execution.
Example:
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
@Override
public String call() throws Exception {
return Thread.currentThread().getName() + " Thread executed!";
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());
System.out.println(future.get()); // Retrieves the result
executor.shutdown();
// pool-1-thread-1 Thread executed!
}
}
Advantages:
Unlike
Runnable
,Callable
allows returning values and throwing exceptions.
4. Using Anonymous Class
Instead of defining a separate class, threads can be created using an anonymous class.
Example:
public class AnonymousExample {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Thread is running...");
}
});
thread.start();
// Thread-0 Thread is running...
}
}
Use Case:
Useful for quick thread creation without creating separate classes.
5. Using Lambda Expressions (Java 8+)
Java 8 introduced lambda expressions, making it even more concise to create threads.
public class AnonymousExample {
// Using lambda expression
public static void main(String[] args) {
Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName() + " Thread is running..."));
System.out.println(Thread.currentThread().getName() + " Current Thread is running...");
thread.start();
/* Output
main Current Thread is running...
Thread-0 Thread is running...
*/
}
}
Advantages:
Reduces boilerplate code for single-method interfaces like
Runnable
.
6. Using ThreadPoolExecutor
(Efficient Thread Management)
ThreadPoolExecutor
(Efficient Thread Management)Instead of creating a new thread every time, thread pools reuse existing threads.
ThreadPoolExecutor
is a low-level API for managing thread pools.
Example:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 5; i++) {
executor.execute(() -> System.out.println(Thread.currentThread().getName() + " is running"));
}
executor.shutdown();
/* Output
pool-1-thread-1 is running
pool-1-thread-2 is running
pool-1-thread-3 is running
pool-1-thread-2 is running
pool-1-thread-1 is running
*/
}
}
Advantages:
Reduces overhead of creating/destroying threads frequently.
Better resource management for high-load applications.
7. Using ForkJoinPool
(Parallel Computing)
ForkJoinPool
(Parallel Computing)The Fork-Join framework is useful for parallel computing by dividing tasks into smaller subtasks.
Example:
package practice;
import java.util.concurrent.RecursiveTask;
public class MyRecursiveTask extends RecursiveTask<Integer> {
private int num;
MyRecursiveTask(int num) {
this.num = num;
}
protected Integer compute() {
if (num <= 1) {
return num;
}
MyRecursiveTask task1 = new MyRecursiveTask(num - 1);
MyRecursiveTask task2 = new MyRecursiveTask(num - 2);
task1.fork();
return task2.compute() + task1.join();
}
}
import java.util.concurrent.ForkJoinPool;
public class ForkJoinExample {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
System.out.println(pool.invoke(new MyRecursiveTask(5))); // 5
}
}
Use Case:
Suitable for divide-and-conquer algorithms like parallel recursion and large dataset processing.
8. Using Virtual Threads
(Java 19+)
Virtual Threads
(Java 19+)Virtual Threads (introduced in Java 19) allow lightweight, high-performance thread execution.
Unlike OS threads, millions of virtual threads can be created without performance issues.
Example:
public class VirtualThreadExample {
public static void main(String[] args) {
Thread.startVirtualThread(() -> System.out.println("Virtual thread running..."));
}
}
Advantages:
Extremely lightweight, highly scalable, and does not consume OS resources like traditional threads.
Ideal for handling highly concurrent workloads efficiently.
Comparison of Thread Creation Methods
Extending Thread
Low
No
Simple tasks
Low
Implementing Runnable
Medium
No
Basic concurrency
Low
Using Callable
& Future
Medium
Yes
Background tasks needing results
Medium
Using Anonymous Class
Medium
No
Quick thread execution
Low
Using Lambda Expressions
Medium
No
Shorter syntax
Low
Using ThreadPoolExecutor
High
No
Managing large tasks efficiently
Medium
Using ForkJoinPool
High
Yes
Parallel processing, recursion
High
Using Virtual Threads
High
No
Highly scalable concurrency
Low
Main Thread and New Thread Running Parallelly
After creating a new thread, the main thread continues to execute concurrently (parallelly) with the newly created thread.
Example
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("Child Thread: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class MainThreadExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // New thread starts
// Main thread continues to execute in parallel
for (int i = 1; i <= 5; i++) {
System.out.println("Main Thread: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/* Output - The output order may vary due to thread scheduling
Child Thread: 1
Main Thread: 1
Main Thread: 2
Child Thread: 2
Main Thread: 3
Child Thread: 3
Main Thread: 4
Child Thread: 4
Main Thread: 5
Child Thread: 5
*/
}
}
How it Works?
The main thread starts execution from
main()
.It creates a new thread (
MyThread
) and starts it usingstart()
.The new thread (
run()
) runs independently while the main thread continues executing its own tasks.Both threads execute concurrently, and the CPU switches between them using the scheduler.
Concurrency vs. Parallelism
Concurrency:
Multiple threads/tasks overlap in execution but may not execute at the exact same instant.
The CPU switches between threads rapidly, giving the illusion of simultaneous execution.
Occurs in single-core or multi-core systems.
Parallelism:
Multiple threads/tasks execute at the exact same time on different CPU cores.
Requires a multi-core processor to truly run tasks simultaneously.
Why "Both threads execute concurrently" and not "parallelly"?
In Java, when we create a new thread, it does not guarantee that it will run on a separate core.
Java threads are managed by the JVM and the OS scheduler, which may time-share them on a single CPU.
If we have a single-core CPU, the threads can only run concurrently by switching back and forth.
If we have a multi-core CPU, the threads may run truly in parallel, but this depends on OS scheduling.
Example of Concurrency vs. Parallelism
1. Concurrent Execution (Single-Core CPU)
Time | Thread-1 | Thread-2
--------------------------------
T1 | Running | -
T2 | - | Running
T3 | Running | -
T4 | - | Running
(Threads switch rapidly, but only one runs at a time.)
2. Parallel Execution (Multi-Core CPU)
Time | Thread-1 | Thread-2
--------------------------------
T1 | Running | Running
T2 | Running | Running
T3 | Running | Running
(Both threads run simultaneously on different CPU cores.)
Last updated