Threads
Last updated
Was this helpful?
Last updated
Was this helpful?
Multithreading is a Java feature that allows concurrent execution of two or more parts of a program for maximum utilization of CPU. Each part of such program is called a thread. So, threads are light-weight, independent path of execution within a program. Threads allow concurrent execution, enabling multiple tasks to be performed simultaneously.
The life cycle of a thread in Java consists of several stages, and understanding these stages is crucial for effective thread management. Understanding the life cycle of a thread in Java allows developers to write efficient and robust multithreaded applications. By properly managing threads and their transitions between stages, you can ensure smooth execution and avoid issues like deadlocks or resource contention.
Let's explore each stage in detail with an example.
New: In the new stage, a thread is created but not yet started. The thread is in this stage when the Thread
object is instantiated using the new
keyword or by extending the Thread
class. For example:
Runnable: In the runnable stage, the thread is ready to run but not currently executing. The thread can be in this stage after calling the start()
method on the Thread
object. For example:
Running: In the running stage, the thread is actively executing its code. It moves from the runnable stage to the running stage when the scheduler selects it for execution. The code within the run()
method of the Thread
class or a class implementing the Runnable
interface is executed in this stage.
Blocked: A thread enters the blocked stage when it is temporarily unable to run. This can occur if the thread is waiting for a resource, such as a lock or input/output operation, to become available. Once the blocking condition is resolved, the thread moves back to the runnable stage.
Waiting: Threads enter the waiting stage when they are waiting for another thread to perform a particular action. This can happen when a thread invokes the wait()
method on an object or waits indefinitely for a notification from another thread.
Timed Waiting: In the timed waiting stage, a thread waits for a specified period of time. This can occur when a thread calls methods like sleep()
or join()
, specifying a time duration for the thread to wait.
Terminated: A thread enters the terminated stage when its run()
method completes execution or when the thread is explicitly stopped by invoking the stop()
method. Once a thread is terminated, it cannot be restarted.
It's important to note that thread scheduling and transitions between these stages are managed by the Java Virtual Machine (JVM) and the operating system. Developers have limited control over the scheduling, but they can influence it using methods like yield()
and interrupt()
.
code snippet demonstrating the thread life cycle:
Threads can be created by using two mechanisms :
Extending the Thread class
Implementing the Runnable Interface
We create a class that extends the java.lang.Thread class. This class overrides the run() method available in the Thread class. A thread begins its life inside run() method. We create an object of our new class and call start() method to start the execution of a thread. Start() invokes the run() method on the Thread object. For example:
In this example, we create a subclass ThreadExample
that extends the Thread
class. The class has a constructor that takes an integer id
to identify each thread. The run()
method is overridden to define the task that the thread should perform.
Inside the run()
method, we print a message indicating that the thread is running, simulate some work by calling Thread.sleep()
, and then print a message indicating that the thread has finished.
In the main()
method, we create two instances of ThreadExample
and start them by calling the start()
method. This causes each thread to execute its run()
method in a separate thread of execution. We also use the join()
method to wait for the threads to finish before the main thread continues execution.
We create a new class which implements java.lang.Runnable interface and override run() method. Then we instantiate a Thread object and call start() method on this object.
For example:
In this example, we create a class RunnableExample
that implements the Runnable
interface. The class has a constructor that takes an integer id
to identify each thread. The run()
method is implemented to define the task that the thread should perform.
Inside the run()
method, we print a message indicating that the thread is running, simulate some work by calling Thread.sleep()
, and then print a message indicating that the thread has finished.
In the main()
method, we create two instances of RunnableExample
. Then we create two Thread
objects, passing the respective Runnable
instances as parameters. Finally, we start the threads by calling start()
method.
As before, the main thread waits for the two threads to finish using the join()
method, and then prints its own completion message.
Using the Runnable
interface allows for better flexibility as it separates the task from the thread itself. Multiple threads can share the same Runnable
instance, which can be useful in certain scenarios.
Runnable
and Thread
The differences between using the Runnable
interface and extending the Thread
class in Java for creating threads:
Runnable
Interface
Thread
Class
Inheritance
Does not require extending a specific class.
Requires extending the Thread
class.
Reusability
Can be used to implement multiple interfaces.
Cannot be used if the class needs to extend another class.
Separation of Concerns
Supports better separation of concerns by separating the task logic from thread management.
Combines both the task logic and thread management in a single class.
Code Structure
The run()
method containing task logic is implemented separately and passed to a Thread
object.
The task logic is implemented directly within the Thread
class by overriding the run()
method.
Object-Oriented Design
Promotes composition over inheritance, as the class implementing Runnable
can be used with any class that accepts a Runnable
object.
Uses inheritance to create a new class that is a specialized version of the Thread
class.
Resource Sharing
Encourages better resource sharing and avoids resource contention, as multiple threads can share the same Runnable
instance.
Each Thread
instance has its own separate resources.
Concurrency Control
Provides a more flexible approach for concurrency control, as multiple Runnable
objects can be executed by a pool of threads managed by an ExecutorService
.
Provides less control over thread execution and management, as each Thread
instance manages its own execution.
Thread Creation
Requires passing the Runnable
instance to a Thread
object for thread creation.
Thread creation is direct, as the Thread
instance is already created.
Extending Other Classes
Allows extending other classes or implementing other interfaces alongside Runnable
.
Limits the ability to extend other classes alongside Thread
.
Both approaches have their merits depending on the specific requirements of your application. However, in general, using the Runnable
interface is more recommended due to its flexibility, better separation of concerns, and support for composition and resource sharing.
In Android, you can use threads to perform time-consuming tasks in the background and keep the main UI thread responsive. However, Android provides higher-level abstractions for managing concurrent operations, such as AsyncTask
, Handler
, and ThreadExecutor
. These abstractions handle thread management and synchronization for you, making it easier to work with threads in an Android application.
Here's an example of using the Thread
class directly in an Android Java application:
In this example, we create a new thread in the onCreate()
method of an Activity
class. Inside the thread's run()
method, we perform a time-consuming task (simulated by the Thread.sleep()
method) and then update the UI on the main thread using runOnUiThread()
. This is necessary because UI updates must be performed on the main thread in Android.
By creating a new thread, we ensure that the time-consuming task runs in the background, keeping the main UI thread responsive. This prevents the UI from freezing or becoming unresponsive while the task is running.
It's important to note that starting threads directly in an Android application should be used with caution. Android provides higher-level abstractions like AsyncTask
, Handler
, and ThreadExecutor
, which handle thread management and synchronization more efficiently. These abstractions simplify the process of working with threads and are often recommended for handling background tasks in an Android application.