Java tutorials > Multithreading and Concurrency > Threads and Synchronization > How to create threads (Thread vs Runnable)?
How to create threads (Thread vs Runnable)?
In Java, multithreading allows concurrent execution of multiple parts of a program. Creating threads is a fundamental aspect of utilizing multithreading capabilities. There are two primary ways to create threads in Java: by extending the Thread
class or by implementing the Runnable
interface. Each approach has its own advantages and disadvantages. This tutorial will walk you through both methods with clear examples.
Creating Threads by Extending the Thread Class
This method involves creating a new class that inherits from the Thread
class. You then override the run()
method to define the task that the thread will execute. The start()
method is crucial; it initiates the thread and calls the run()
method in a new thread of execution. Calling run()
directly simply executes the code within the current thread, defeating the purpose of multithreading. Using Thread.currentThread().getName()
provides insight into which thread is currently running.
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread thread1 = new MyThread();
thread1.start(); // Starts the thread and calls the run() method
MyThread thread2 = new MyThread();
thread2.start();
}
}
Creating Threads by Implementing the Runnable Interface
This approach involves implementing the Runnable
interface. You create a class that implements Runnable
and provides a run()
method defining the task for the thread. Then, you create a new Thread
object, passing your Runnable
implementation as an argument to the Thread
constructor. Finally, you call start()
on the Thread
object to begin execution in a separate thread. This approach promotes better separation of concerns and allows your class to inherit from another class if needed.
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread running: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
thread1.start();
Thread thread2 = new Thread(myRunnable);
thread2.start();
}
}
Concepts Behind the Snippets
Extending Thread: This approach directly couples the thread's behavior with the class definition. It's straightforward but less flexible as Java doesn't allow multiple inheritance. Your class becomes a specialized type of Thread. Implementing Runnable: This is more flexible. You define the task to be executed in the run()
method, and then create a Thread
object to execute that task. This decoupling is beneficial because your class can extend another class, adhering to the principle of single responsibility and facilitating better code organization.
Real-Life Use Case
Imagine a server application handling multiple client requests concurrently. Each request can be processed in a separate thread. Using Runnable
is preferred in such scenarios because the request handler class might need to extend another class related to networking or data processing. For example, a web server might use a thread pool where each thread executes a Runnable
task representing a client request.
Best Practices
Runnable
: In most cases, implementing Runnable
is the preferred approach because it provides greater flexibility and promotes better code organization.ExecutorService
, which manages thread pools and simplifies thread management.run()
method are properly handled to prevent thread termination and potential data corruption.synchronized
blocks or Lock
objects to prevent race conditions and ensure data consistency.
Interview Tip
Be prepared to discuss the differences between extending Thread
and implementing Runnable
, including the benefits of each approach. Also, be ready to explain scenarios where one approach is more appropriate than the other. Understanding thread lifecycle and synchronization is crucial.
When to use them
Thread
does not conflict with other design requirements. It is more straightforward for simple cases.
Memory footprint
The memory footprint of a thread depends on various factors like the stack size and the JVM implementation. There's no significant difference in memory footprint between a thread created by extending Thread
and a thread created using Runnable
. The memory used by the Thread
object itself is relatively small.
Alternatives
Alternatives to creating threads directly include using ExecutorService
for managing thread pools, using ForkJoinPool
for divide-and-conquer algorithms, and using reactive programming libraries like RxJava or Project Reactor for asynchronous and event-driven programming.
Pros and Cons of Extending Thread
Pros:
Cons:
- Limits inheritance capabilities, as Java only allows single inheritance.
- Tightly couples the task to the thread, reducing flexibility.
Pros and Cons of Implementing Runnable
Pros:
Cons:
- Slightly more verbose compared to extending
Thread
.
FAQ
-
What happens if I call the
run()
method directly instead ofstart()
?
Calling therun()
method directly will execute the code within the current thread, not in a new thread. It will behave like a regular method call, defeating the purpose of multithreading. -
Can I reuse a
Runnable
object with multiple threads?
Yes, you can create multipleThread
objects using the sameRunnable
instance. EachThread
will execute therun()
method of theRunnable
object concurrently. -
What is the difference between
Thread.sleep()
andThread.yield()
?
Thread.sleep()
pauses the execution of the current thread for a specified amount of time. The thread goes into a waiting state.Thread.yield()
hints to the scheduler that the current thread is willing to relinquish its use of the CPU. The scheduler is free to ignore this hint.