Table of contents
- Introduction to Multithreading in Java
- Table of Contents
- Case 5: Overloading the run() Method
- Understanding the Code Behavior
- How to Call run() with Parameters?
- Explicitly Calling the Overloaded run() Method from Main Thread
- Context Switching in Multi-threading
- Possible Outputs in Multi-threading
- Case-6: Overriding of start() Method
- Code Explanation:
- Output:
- Code Breakdown:
- Summary of the Behavior:
- Key Takeaways:
- Case-7: Overriding start() Method and Using super.start() to Call the Parent Class's start() Method
- Thread Class Implementation Overview:
- Code Explanation with super.start():
- Output:
- Explanation of the Output:
- Summary of the Behavior:
- Key Takeaways:
- Case-8: Life Cycle of a Thread in Java
- 1. Born State (New State)
- 2. Ready/Runnable State
- 3. Running State
- 4. Dead State
- Thread Life Cycle Overview:
- Visual Representation of Thread Life Cycle:
- Explanation of Transitions:
- Summary of Thread Life Cycle Stages:
- Case-9: IllegalThreadStateException in Java
- 7. Creation of Thread Using Runnable Interface
- Why Use the Runnable Interface?
- Creating Threads Using the Runnable Interface
- Functional Interface and Lambda Expressions
- 1. Creating a Thread Using java.lang.Thread Class
- 2. Creating a Thread Using the Runnable Interface
- Explanation of Key Concepts
- How Java Creates a Thread Using Runnable Interface
- Using javap to Explore Classes
- Eclipse Shortcuts for Thread Exploration
- Key Points to Remember
- Code-1: Thread Creation Using Runnable Interface
- Key Points to Remember:
- Code-2: Using public java.lang.Thread() Constructor
- Code Example: Using the Thread() Constructor
- Key Takeaways:
- Code-3: Creating a Thread Using the Runnable Interface
- Key Takeaways:
- Case Study: Thread Creation and Output Analysis
- Case Study: Thread Creation and Output Analysis (Case-2)
- Case Study: Calling t1.run() Instead of t1.start()
- Case Study: Calling t2.run() Directly
- Case Study: Calling r.start()
- Analysis of the Code:
- Error in the Code:
- Key Points:
- Correct Usage:
- Output after the correction:
- Conclusion:
- Case Study: Calling r.run()
- Analysis of the Code:
- Key Points:
- Number of Threads:
- Output:
- Conclusion:
- Analysis of the Cases and Questions
- 1. In which of the above cases a new Thread will be created which is responsible for the execution of MyRunnable's run() method?
- Answer to Question 1:
- 2. In which of the above cases a new Thread will be created?
- Answer to Question 2:
- 3. In which of the above cases MyRunnable's run() method will be executed?
- Answer to Question 3:
- Summary of Answers:
- Different Approaches for Creating a Thread in Java
- A. Extending the Thread Class (Not Recommended)
- B. Implementing the Runnable Interface (Recommended Approach)
- Conclusion:
- Constructors Available in the Thread Class
- 1. Thread t = new Thread()
- 2. Thread t = new Thread(Runnable r)
- 3. Thread t = new Thread(String name)
- 4. Thread t = new Thread(Runnable r, String name)
- 5. Thread t = new Thread(ThreadGroup g, String name)
- 6. Thread t = new Thread(ThreadGroup g, Runnable r)
- 7. Thread t = new Thread(ThreadGroup g, Runnable r, String name)
- 8. Thread t = new Thread(ThreadGroup g, Runnable r, String name, long stackSize)
- Summary of Common Constructors:
- Best Practices:
- How Many Threads are Created and What is the Output?
- Explanation of Code:
- Threads Created:
- Expected Output (One Possible Order):
- Detailed Breakdown of Execution Flow:
- Code Explanation:
- Code:
- Explanation:
- Expected Output:
- Key Takeaway:
- Explanation of Thread Naming in Java:
- Example 1: Getting the Name of the Main Thread and Child Thread
- Explanation:
- Expected Output:
- Example 2: Using run() Method in a Custom Thread
- Explanation:
- Expected Output:
- Key Takeaways:
- Setting and Changing Thread Names in Java
- Example 3: Changing the Name of the Main Thread
- Explanation:
- Expected Output:
- Example 4: Exception in Main Thread
- Explanation:
- Expected Output:
- Example 5: Changing the Name of the Main Thread After Exception
- Explanation:
- Expected Output:
- Example 6: Changing the Name of Two Threads (Main and Child Thread)
- Explanation:
- Expected Output:
- Key Takeaways:
- Conclusion: Multithreading in Java: Thread Creation, Naming, and Handling
- 1. Thread Creation: Extending Thread vs Implementing Runnable
- 2. Thread Constructors
- 3. Thread Execution Process
- 4. Thread Naming and Accessing Thread Information
- 5. Changing Thread Names Dynamically
- 6. Handling Exceptions in Threads
- 7. Conclusion and Best Practices
- Overall Takeaway
Introduction to Multithreading in Java
Multithreading is a fundamental concept in Java that allows for the concurrent execution of multiple threads, enabling efficient utilization of CPU resources and better performance for tasks that can be parallelized. In Java, threads are lightweight, independent units of execution that can run concurrently, performing separate tasks simultaneously within a single program. This ability to execute multiple tasks at the same time is especially useful in scenarios where programs need to handle a variety of operations such as user interactions, I/O operations, or computational tasks.
Java provides robust support for multithreading, both through the Thread
class and the Runnable
interface. These tools allow developers to create and manage threads with ease, making multithreading a core feature for applications that demand high performance, responsiveness, or parallel task execution.
This exploration of multithreading in Java covers various aspects such as:
Creating Threads: We will examine the two main approaches for creating threads in Java—extending the
Thread
class and implementing theRunnable
interface.Managing Thread Execution: We’ll delve into how threads are executed, including how to start them and manage their life cycle, as well as when to use
start()
versusrun()
.Thread Naming: Understanding how to name and identify threads is crucial for debugging multithreaded programs. We will look at how to retrieve and set thread names dynamically.
Best Practices: Best practices for using multithreading efficiently and effectively in real-world applications will also be discussed.
Multithreading offers powerful ways to handle concurrent tasks, improve program efficiency, and create responsive, high-performing applications. However, it also introduces complexity—race conditions, deadlocks, and other concurrency issues—that developers need to manage carefully. This guide aims to provide a comprehensive understanding of how to work with threads in Java and build robust multithreaded applications.
Table of Contents
Case 5: Overloading the
run()
MethodExplanation of
run()
method behavior.Examples of overloading with code and output analysis.
Case 6: Overriding the
start()
MethodWhy overriding
start()
is discouraged.Examples showcasing behavior with overridden
start()
.
Case 7: Using
super.start()
in Overriddenstart()
- How to combine custom logic with the parent class’s
start()
method.
- How to combine custom logic with the parent class’s
Case 8: Life Cycle of a Thread
Thread states: Born, Ready, Running, Dead.
Detailed flowchart and code examples.
Best Practices in Multithreading
When to use thread overloading, overriding, and explicit calls.
Common pitfalls and debugging tips.
Conclusion and Key Takeaways
Case 5: Overloading the run()
Method
In Java, we can overload the run()
method of the Thread
class. This allows us to define multiple run()
methods with different parameters. However, it’s essential to understand the behavior when overloading the run()
method.
Key Point:
Although we can overload the run()
method, the start()
method of the Thread
class will always invoke the no-argument version of the run()
method. If we overload run()
with arguments, the thread will still call the zero-argument run()
method unless we explicitly call the overloaded run()
method within the thread.
Here’s an example:
class MyThread extends Thread {
// No-argument run method
public void run() {
System.out.println("No-argument method");
}
// Overloaded run method with an argument
public void run(int i) {
System.out.println("Method with integer argument: " + i);
}
}
public class ThreadDemo {
public static void main(String... args) {
MyThread t = new MyThread();
t.start(); // Invokes run() with zero arguments
}
}
Output:
No-argument method
Which run()
method is called by JVM?
When we invoke t.start()
, the thread calls the run()
method with zero arguments, even though we have overloaded the method. This is because start()
invokes the run()
method with no parameters. The overloaded run(int i)
method is not called automatically.
Understanding the Code Behavior
Let's break down the flow of the program when we call t.start()
:
The
start()
method of theThread
class is invoked.This method internally calls the
run()
method with no arguments.The
run()
method executes the task defined in it and prints the message "No-argument method".
After the thread completes its execution, the main thread proceeds to execute the rest of the code, which could include further processing or printing messages.
class MyThread extends Thread {
public void run() {
System.out.println("No-argument run method");
run(5); // Explicitly calling the overloaded run method
}
public void run(int i) {
System.out.println("Int argument run method: " + i);
}
}
public class Multithreading8 {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // Start the thread
for (int i = 0; i < 5; i++) {
System.out.println("Main thread");
}
}
}
Output:
Main thread
Main thread
Main thread
Main thread
Main thread
No-argument run method
Int argument run method: 5
Code Explanation:
Initially, the
start()
method is called, and the thread'srun()
method with zero parameters is invoked. This prints"No-argument run method"
.The
run()
method with an integer argument is explicitly called within therun()
method itself, printing"Int argument run method: 5"
.Meanwhile, the main thread executes its own task and prints
"Main thread"
five times.
This demonstrates how threads work concurrently, with context switching happening between the threads managed by the Java Virtual Machine (JVM) and the thread scheduler.
How to Call run()
with Parameters?
If you want to call the run()
method with parameters, you can do so by invoking it explicitly within the run()
method. Here's an example where we explicitly call the overloaded run(int i)
method from within the run()
method:
class MyThread extends Thread {
public void run() {
System.out.println("No-argument run method");
run(5); // Explicitly calling the overloaded run method
}
public void run(int i) {
System.out.println("Int argument run method: " + i);
}
}
public class Multithreading8 {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // Start the thread
for (int i = 0; i < 5; i++) {
System.out.println("Main thread");
}
}
}
Output Explanation:
The
run()
method with zero arguments is called by the JVM through thestart()
method.The zero-argument
run()
method calls the overloadedrun(int i)
method explicitly.The main thread continues to execute its own tasks.
Explicitly Calling the Overloaded run()
Method from Main Thread
You can also explicitly call the overloaded run()
method from the main thread (or any other thread) without using the start()
method. However, this will not start a new thread; it will simply call the method as a normal method of the class.
class MyThread extends Thread {
public void run() {
System.out.println("No-argument run method");
run(5); // Calling overloaded run method
}
public void run(int i) {
System.out.println("Int argument run method");
}
}
public class Multithreading8 {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // This calls run() with zero arguments
t.run(5); // This explicitly calls the overloaded run() method
}
}
Output:
No-argument run method
Int argument run method
In this case, after t.start()
calls the run()
method with zero arguments, we explicitly call the overloaded run(int i)
method from the main thread. Note that this doesn’t invoke multi-threading behavior since it’s executed in the main thread.
Context Switching in Multi-threading
When multiple threads are running concurrently, the JVM manages the switching between threads using a concept known as context switching. The JVM and the underlying operating system handle thread scheduling and switching between threads. This is critical to achieving true multi-threading.
For example:
Thread
t1
starts executing, and before it finishes, the JVM may suspend it and start executing threadt2
.The JVM switches between the two threads, ensuring they execute in parallel (depending on the number of processors and the OS scheduler).
In this example, context switching happens between the main thread and the new thread t
. The main thread prints "Main thread" multiple times while the new thread executes its run()
method.
Possible Outputs in Multi-threading
Due to the non-deterministic nature of thread scheduling, the output of a multi-threaded program can vary. Here are a few possible outputs:
Output 1:
Int argument run method
No-argument run method
Main thread
Main thread
Main thread
Main thread
Main thread
Output 2:
No-argument run method
Int argument run method
Main thread
Main thread
Main thread
Main thread
Main thread
Case-6: Overriding of start()
Method
Concept:
Overriding
start()
: If we override thestart()
method in theThread
class, ourstart()
method will be executed like a normal method, but no new thread will be created, and no new thread will be started. This prevents the creation of a new thread and blocks the thread scheduler from informing about it.Multithreading is not implemented in this case because overriding the
start()
method stops the creation of a new thread.Recommendation: It is never recommended to override the
start()
method. The primary duty is to override therun()
method, which is the task-defining method for threads.
How Many Threads Are There in the Code and What Is the Output?
Thread Behavior:
Here, the
start()
method present in theMyThread
class is called, but there is no possibility of creating a new thread and informing the thread scheduler about it.Since the overridden
start()
method inMyThread
is called, no new thread is created.The
start()
method inMyThread
will be executed by the main thread only, and hence multithreading is not implemented in this case.Multithreading is only implemented when the
start()
method in theThread
class is called. In this case, we are overriding thestart()
method, which prevents the creation of a new thread.Overriding the
run()
method is our prime duty to define the task for the thread, but overridingstart()
should be avoided.
Code Explanation:
class MyThread extends Thread {
@Override
public void start() {
// Custom start method, but no new thread is created
System.out.println("start method is called");
}
public void run() {
// Run method that would be executed in the thread, but it will not run in a new thread here
System.out.println("No argument run method");
}
}
public class Multithreading9 {
public static void main(String[] args) {
MyThread t = new MyThread();
// Here, since our class's start() method is called, no new thread is created.
t.start();
// Task for the main thread (this will execute in the main thread)
for (int i = 0; i < 5; i++) {
System.out.println("Main thread");
}
}
}
Output:
start method is called
Main thread
Main thread
Main thread
Main thread
Main thread
Code Breakdown:
Overridden
start()
Method:The
start()
method inMyThread
is called by the main thread, which prints"start method is called"
.No new thread is created, and the
start()
method does not initiate multithreading.
run()
Method:The
run()
method inMyThread
is defined, but it is not called by the thread scheduler because no new thread is created by calling the overriddenstart()
method.The
run()
method would normally be executed in a new thread, but since there is no new thread, it doesn't run.
Main Thread:
The
main
method proceeds to execute the task intended for the main thread.The loop prints
"Main thread"
five times as part of the main thread’s execution.
Summary of the Behavior:
Overridden
start()
method: When thestart()
method is overridden, the task for multithreading is skipped. Thestart()
method is executed like any normal method and does not create a new thread.No New Thread Created: Since the overridden
start()
method does not create a new thread, multithreading is not achieved in this case.Execution: The overridden
start()
method runs in the main thread, and the main thread continues to execute its task.No Context Switching: There is no context switching between threads since there is only the main thread.
Key Takeaways:
Overriding
start()
: When you override thestart()
method, no new thread is created, and it behaves like a regular method.Multithreading: Multithreading is not implemented if
start()
is overridden.Best Practice: The best practice is to override the
run()
method for defining the thread's task, but do not override thestart()
method.Recommendation: Always rely on the
start()
method of theThread
class to initiate multithreading properly.
Case-7: Overriding start()
Method and Using super.start()
to Call the Parent Class's start()
Method
Concept:
Overriding the
start()
Method: In this case, thestart()
method is overridden in the user-definedMyThread
class. Thesuper.start()
is used to invoke thestart()
method from the parent classThread
, which is responsible for starting a new thread.How Many Threads Will Be Created?
2 threads will be created in this case:
Main Thread: The main thread calls the overridden
start()
method and executes its task.User-Defined Thread: The new thread, created by the
Thread
class'sstart()
method, executes therun()
method in theMyThread
class.
Code Explanation:
Line 1 (
t.start()
):- In the
main()
method, we call thestart()
method of theMyThread
objectt
. This triggers the overriddenstart()
method inMyThread
.
- In the
Line 2 (
public void start()
):We override the
start()
method in theMyThread
class.The first action inside the overridden
start()
method is to callsuper.start()
.super.start()
calls thestart()
method of the parent class (Thread
), which handles the creation of a new thread and registers it with the thread scheduler.This starts a new thread and invokes the
run()
method.
Line 3 (
super.start()
):The
super.start()
call creates a new thread, and therun()
method inMyThread
is executed by this new thread.After that, the control returns to the
main()
method, and the task in the main thread is executed.Since we are working with two threads, context switching will occur between the threads managed by the JVM's thread scheduler.
Context Switching:
Context switching happens between the main thread and the user-defined thread.
The JVM schedules the execution of both threads, so both can run concurrently.
Thread Class Implementation Overview:
The Thread
class is the heart of multi-threading. The start()
method is crucial for creating and managing threads.
class Thread {
// Heart of Multi-threading
public void start() {
// 1. Register the thread with the Thread Scheduler
// 2. Perform all the mandatory low-level activities (Memory level)
// 3. Call the run() method
// The run() method is invoked, which in this case will be in the MyThread class
}
}
Code Explanation with super.start()
:
class MyThread extends Thread {
@Override
public void start() { // --> Line 2
// super.start() calls the parent class's start() method
super.start(); // --> Line 3
System.out.println("start method is called");
}
@Override
public void run() {
System.out.println("No argument run method");
}
}
public class Multithreading10 {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // --> Line 1
// Task for main thread
System.out.println("Main thread");
}
}
Output:
start method is called
Main thread
No argument run method
Explanation of the Output:
start()
Method Call:- The
main()
method calls thestart()
method of theMyThread
objectt
at Line 1. This triggers the overriddenstart()
method in theMyThread
class.
- The
Calling
super.start()
:Inside the overridden
start()
method, thesuper.start()
method is called. This starts a new thread, and therun()
method is executed in that new thread.The output
"start method is called"
is printed from thestart()
method.
Main Thread Execution:
- After calling
super.start()
, control returns to themain()
method, and the main thread continues its execution, printing"Main thread"
.
- After calling
User-Defined Thread Execution:
- The new thread, which was started by
super.start()
, executes therun()
method ofMyThread
. This prints"No argument run method"
.
- The new thread, which was started by
Summary of the Behavior:
Two Threads Created:
Main thread: Executes the
main()
method and prints"Main thread"
.User-defined thread: Created by
super.start()
and executes therun()
method, printing"No argument run method"
.
Context Switching:
- Context switching occurs between the main thread and the user-defined thread managed by the JVM's thread scheduler. Both threads run concurrently, leading to unpredictable execution order.
Best Practice:
- Overriding
start()
is generally not recommended, as it's better to just override therun()
method and rely on the parentThread
class to handle thread creation.
- Overriding
Key Takeaways:
Two Threads Created: One from the main method and one from the
Thread
class'sstart()
method.Use of
super.start()
: Invokingsuper.start()
in the overriddenstart()
method ensures the creation of a new thread.Context Switching: The JVM handles the execution of both threads, and context switching happens between them.
Thread Execution Order: Since both threads run concurrently, the output order can vary.
Case-8: Life Cycle of a Thread in Java
In Java, the thread life cycle describes the various stages a thread goes through from creation to completion. Below is a detailed explanation of each state in the thread's life cycle:
1. Born State (New State)
Thread Object Creation:
When a thread is created, it is in the born state or new state. At this point, the thread object has been instantiated but has not yet started execution.MyThread t = new MyThread(); // Thread is in born (new) state
Explanation:
A thread enters the born state when an instance of theThread
class (or its subclass) is created. However, it does not start executing yet.
2. Ready/Runnable State
Calling
start()
Method:
After a thread object is created, thestart()
method is called. This moves the thread to the ready or runnable state, meaning it is now ready to be executed by the Thread Scheduler.t.start(); // Thread is in ready/runnable state
Explanation:
Thestart()
method doesn't directly run therun()
method of the thread. Instead, it tells the Thread Scheduler to begin managing this thread, making it available for execution. At this point, the thread is ready to be allocated CPU time.
3. Running State
Thread Scheduler Allocates CPU Time:
If the Thread Scheduler allocates CPU time to the thread, it enters the running state. Here, the thread begins executing itsrun()
method.Explanation:
The Thread Scheduler decides which thread gets to run based on various factors such as priority and availability of CPU resources. If the thread is selected by the scheduler, it enters the running state and executes itsrun()
method.
4. Dead State
Completion of
run()
Method:
Once the thread'srun()
method has finished executing, the thread enters the dead state. A thread cannot be started again after it reaches the dead state.Explanation:
The thread finishes its task, and there is no further work to be done by the thread. Once therun()
method completes, the thread is considered dead and is no longer usable.
Thread Life Cycle Overview:
The life cycle of a thread follows these stages:
Born State (New):
- Thread object is created using
new
keyword, but it has not yet started execution.
- Thread object is created using
Ready/Runnable State:
- After calling the
start()
method, the thread enters the ready or runnable state, waiting for CPU time.
- After calling the
Running State:
- When the Thread Scheduler allocates CPU time, the thread enters the running state and begins executing its
run()
method.
- When the Thread Scheduler allocates CPU time, the thread enters the running state and begins executing its
Dead State:
- After the
run()
method completes, the thread enters the dead state, signifying that the thread has finished its execution and cannot be reused.
- After the
Visual Representation of Thread Life Cycle:
Born State
|
v
Ready/Runnable State
|
v
Running State
|
v
Dead State
Explanation of Transitions:
Born State to Ready/Runnable:
- The thread enters the ready/runnable state when the
start()
method is called on the thread object.
- The thread enters the ready/runnable state when the
Ready/Runnable to Running:
- The Thread Scheduler allocates CPU time to the thread, and it moves into the running state, where the
run()
method is executed.
- The Thread Scheduler allocates CPU time to the thread, and it moves into the running state, where the
Running to Dead:
- When the thread finishes executing its
run()
method, it enters the dead state.
- When the thread finishes executing its
Summary of Thread Life Cycle Stages:
Born State (New): The thread has been created but not yet started.
Ready/Runnable State: The thread is ready to run, waiting for the CPU.
Running State: The thread is executing its
run()
method.Dead State: The thread has finished its execution and cannot be restarted.
This life cycle is essential for understanding how threads work in Java, and it helps in efficiently managing threads to ensure optimal performance in multi-threaded applications.
Case-9: IllegalThreadStateException in Java
In Java, once a thread is started using the start()
method, it cannot be started again. If you try to call start()
on an already started thread, the JVM throws an IllegalThreadStateException
at runtime. This occurs because a thread can only be started once, and attempting to restart it leads to an illegal operation.
Explanation of the Issue
When a thread is created and started, it goes through the states of the thread life cycle. Once the thread enters the running state and completes its task (i.e., the run()
method finishes executing), the thread is considered dead. A thread that has already been started cannot be restarted or re-executed.
Attempting to start an already started thread causes the IllegalThreadStateException
to be thrown.
Code Example
class MyThread extends Thread {
public void run() {
System.out.println("No argument run method");
}
}
public class Multithreading12 {
public static void main(String[] args) {
MyThread t = new MyThread();
// Calling start() of MyThread - First time
t.start(); // --> Line-1
// Calling start() of the same thread - Second time (This will throw IllegalThreadStateException)
t.start();
// Task for the main thread
System.out.println("Main thread");
}
}
Explanation of the Code
First
t.start()
Call:- The
start()
method is called for the first time. The thread enters the ready/runnable state and eventually starts executing therun()
method. The output is"No argument run method"
from therun()
method.
- The
Second
t.start()
Call:- The second attempt to call
start()
on the same threadt
causes theIllegalThreadStateException
. This is because once a thread is started, it cannot be started again. This leads to a runtime exception being thrown.
- The second attempt to call
Main Thread Task:
- The
main
method continues and prints"Main thread"
, which will be displayed after the exception is thrown, if the exception is not caught.
- The
Output
No argument run method
Exception in thread "main" java.lang.IllegalThreadStateException
at java.base/java.lang.Thread.start(Thread.java:793)
at new2.main(new2.java:17)
Cause of the Exception
- The thread
t
has already been started once, and attempting to start it again leads to theIllegalThreadStateException
. This exception is part of the runtime exceptions in Java, meaning it occurs during the execution of the program.
Key Points to Remember
IllegalThreadStateException
is thrown if you attempt to start a thread that has already been started.A thread in Java can only be started once; attempting to restart it will result in a runtime exception.
The
start()
method should only be invoked once on a thread. Once a thread has completed its task (i.e., therun()
method has finished), it cannot be restarted.
This exception is a common pitfall when working with threads in Java. Understanding the life cycle of a thread and managing thread states properly is crucial to avoid such errors in multi-threaded applications.
Here’s a detailed explanation using each and every word from your text, ensuring clarity and completeness:
7. Creation of Thread Using Runnable Interface
In Java, there are two common ways to create a thread:
Using the
Thread
Class(extending it directly):Use the
start()
method from theThread
class to initiate a thread.Override the
run()
method to define the job or task of the thread.
Using the
Runnable
Interface(implementing it and passing it to aThread
object):Implement the
java.lang.Runnable
interface.Provide an implementation for the
run()
method.Pass the
Runnable
object to aThread
instance and callstart()
to execute the thread.
Exploring Runnable
Using javap
Command:
javap java.lang.Runnable
The output of this command is:
Compiled from "Runnable.java"
public interface java.lang.Runnable {
public abstract void run();
}
The
Runnable
interface contains a single abstract methodrun()
.A functional interface has exactly one abstract method, enabling the use of lambda expressions in Java.
Why Use the Runnable
Interface?
Challenges Addressed by Sun Microsystems (SUNMS):
SUNMS designed the
Runnable
interface as part of their Software Requirement Specifications (SRS) for creating threads in Java.An interface represents the SRS by defining rules that developers must follow to create threads.
Directly implementing all the required features would be cumbersome for developers. SUNMS provided the
Thread
class as an adapter to reduce this burden.
Role of the Thread
Class as an Adapter:
Performs low-level tasks such as:
Registering the thread with the Thread Scheduler.
Handling memory-level activities.
Invoking the
run()
method.
Developers only need to focus on the job or task for the thread by implementing the
run()
method.
Example of Thread
Class as an Adapter
interface Runnable {
public abstract void run();
}
class Thread implements Runnable { // Adapter class
public void start() {
// 1. Register the thread with the Thread Scheduler
// 2. Perform low-level activities (memory management)
// 3. Call the run() method
}
public void run() {
// Define the job for the thread
}
}
Creating Threads Using the Runnable
Interface
Example Code
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Task executed by Runnable thread.");
}
}
public class RunnableExample {
public static void main(String[] args) {
MyRunnable task = new MyRunnable(); // Task defined
Thread t = new Thread(task); // Pass task to Thread
t.start(); // Start the thread
}
}
Functional Interface and Lambda Expressions
- Since
Runnable
is a functional interface, it allows lambda expressions for concise thread creation.
Example with Lambda Expression
public class LambdaExample {
public static void main(String[] args) {
Runnable task = () -> System.out.println("Task executed with Lambda!");
Thread t = new Thread(task);
t.start();
}
}
1. Creating a Thread Using java.lang.Thread
Class
Step 1: You can create a thread by extending the
Thread
class.Step 2: Override the
run()
method to define the task or job the thread will perform.Step 3: Call the
start()
method to begin the thread's execution.
Code Example (Using Thread Class)
class MyThread extends Thread {
@Override
public void run() {
// This is the task for the thread
System.out.println("Thread is running.");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // Starts the thread, which will execute the run() method
}
}
2. Creating a Thread Using the Runnable
Interface
Why Use the Runnable
Interface?
SRS (Software Requirement Specifications): Sun Microsystems introduced the
Runnable
interface to provide a standardized way of creating threads. Instead of forcing developers to extend theThread
class, which can lead to limitations (since Java supports single inheritance), theRunnable
interface allows developers to separate the thread’s task from the thread's management.Flexibility: The
Runnable
interface offers a more flexible approach for creating threads, as it allows any class to implement the interface, while still extending another class.Functional Interface: The
Runnable
interface contains only one abstract methodrun()
, which makes it a functional interface. This allows it to be used with lambda expressions in Java 8 and beyond.
Runnable Interface Code Example
MyRunnable implements Runnable {
@Override
public void run() {
// This is the task for the thread
System.out.println("Runnable thread is running.");
}
}
public class RunnableExample {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t = new Thread(myRunnable); // Pass Runnable to Thread
t.start(); // Starts the thread
}
}
Explanation of Key Concepts
Runnable Interface:
TheRunnable
interface in Java defines a single method:run()
. Therun()
method contains the code that will be executed when the thread starts. The interface makes the thread task independent of the thread management, allowing the same task to be used across multiple threads.Thread Class:
TheThread
class in Java implements theRunnable
interface, meaning that it can accept anyRunnable
object and execute itsrun()
method. TheThread
class provides higher-level management features like starting the thread, controlling the thread’s state, etc.
How Java Creates a Thread Using Runnable
Interface
Thread Management:
TheThread
class is often referred to as an adapter class because it helps reduce the burden on developers. It does the low-level work (e.g., memory management, thread scheduling) that would otherwise be cumbersome for developers.Adapter Class:
TheThread
class can be thought of as an adapter because it provides default implementations for thread-related tasks, while the developer is only responsible for implementing the task (i.e., therun()
method).Separation of Task and Thread Management:
With theRunnable
interface, the thread’s task is decoupled from the thread management. The developer can focus on the task itself, while theThread
class takes care of managing the thread.
Using javap
to Explore Classes
Command 1:
javap java.lang.Runnable
This command shows the definition of theRunnable
interface. It confirms thatRunnable
is a functional interface with one abstract method,run()
.public interface java.lang.Runnable { public abstract void run(); }
Command 2:
javap java.lang.Thread
This reveals thatThread
implements theRunnable
interface, so it is capable of handling tasks defined in therun()
method.Thread implements Runnable { public void start() { // Registers the thread with the thread scheduler // Other low-level activities // Calls run() method } public void run() { // Job for the thread } }
Eclipse Shortcuts for Thread Exploration
Ctrl + Shift + T
:
Opens the definition of any class. Use this to view the definition of theThread
class or any other class.Ctrl + O
:
Lists all the methods inside the class, allowing you to easily navigate through the methods of theThread
class or any other class.
Key Points to Remember
Functional Interface:
Runnable
is a functional interface, which makes it compatible with Java’s lambda expressions, offering a more concise way to create thread tasks.Advantages of Runnable:
Allows any class to implement
Runnable
without having to extendThread
.Enables a thread's task to be reused across different threads.
Increases flexibility by separating thread creation from task definition.
Thread
as an Adapter Class:- The
Thread
class simplifies thread management by providing default implementations for creating and starting threads, leaving only the task (i.e., therun()
method) for the developer to implement.
- The
Code-1: Thread Creation Using Runnable Interface
Code Explanation
Object Creation:
- A
MyThread
object is created in themain
method.
- A
start() Method and Multithreading:
The
start()
method is considered the heart of multithreading.However, in this case, the
start()
method is not defined in theMyThread
class.
JVM Checks for start() Method:
The JVM first checks for the
start()
method in theMyThread
class.Since the
start()
method is not present inMyThread
, the JVM checks in its parent class, which isObject
.The
start()
method is not found in theObject
class either.
Parent Class Hierarchy:
Class Level:
Object
is the ultimate parent ofMyThread
, and as mentioned, it doesn't have astart()
method.The JVM checks
MyThread
andObject
for the method but doesn't find it.
Interface Level:
The JVM also checks the
Runnable
interface for thestart()
method, but it’s not defined in theRunnable
interface either.Since the
start()
method is not found in eitherObject
orRunnable
, it throws aClassNotFoundException
.
Where start() Method Is Found:
- The
start()
method is actually present in theThread
class, which is designed to handle multithreading.
- The
Code for Thread Creation Using Runnable Interface
// Runnable Interface
interface Runnable {
public abstract void run(); // This is the method that will be executed in a new thread
}
// Thread Class Implementing Runnable
class Thread implements Runnable { // Adapter class for Runnable interface
public void start() {
// 1. Register the thread with ThreadScheduler
// 2. Perform mandatory low-level memory activities
// 3. Invoke or call run() method
}
public void run() {
// Define the task for the thread to execute
}
}
// MyThread Class Implementing Runnable
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 1; i < 10; i++) {
System.out.println("Child Thread");
}
}
}
public class Multithreading13 {
public static void main(String[] args) {
MyThread t = new MyThread(); // Create the thread object
// Start the thread (incorrect usage)
t.start(); // This will throw an error because 'start()' is not defined in MyThread
// Task for the main thread
for (int i = 1; i <= 10; i++) {
System.out.println("Main Thread");
}
}
}
Explanation of the Error:
Error: Could not find or load main class Multithreading13
Caused by: java.lang.ClassNotFoundException: Multithreading13
The error occurs because of the incorrect usage of the start()
method in MyThread
. MyThread
implements Runnable
, but it does not extend Thread
, so it cannot directly call start()
. The start()
method is defined in the Thread
class, not the Runnable
interface.
How to Fix the Code:
To fix this issue, we need to ensure that the MyThread
class is either extended from Thread
or that we create a Thread
object with MyThread
as its Runnable
task.
Fixed Code Example:
// MyThread Class Implementing Runnable
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 1; i < 10; i++) {
System.out.println("Child Thread");
}
}
}
public class Multithreading13 {
public static void main(String[] args) {
MyThread t = new MyThread(); // Create the Runnable task
Thread thread = new Thread(t); // Pass the task to a Thread object
thread.start(); // Start the thread
// Task for the main thread
for (int i = 1; i <= 10; i++) {
System.out.println("Main Thread");
}
}
}
Output:
With the corrected code, the output will be:
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Child Thread
Child Thread
Child Thread
Child Thread
Child Thread
Child Thread
Child Thread
Child Thread
Child Thread
Child Thread
The main thread and the child thread will print their respective tasks.
The
start()
method works correctly here because theMyThread
task is wrapped in aThread
object, which is responsible for managing the multithreading.
Key Points to Remember:
start() Method is the heart of multithreading and is defined in the
Thread
class.Runnable Interface: It only defines the
run()
method, which represents the task to be executed by a thread.Thread Class: It is responsible for managing threads and has the
start()
method, which initiates the execution of therun()
method.Common Mistake: You cannot call
start()
directly on a class that only implementsRunnable
. You must create aThread
object and pass theRunnable
task to it.
Code-2: Using public java.lang.Thread()
Constructor
Explanation:
Thread Creation Using Zero-Parameter Constructor:
By using the
Thread()
constructor with zero parameters, thestart()
method of theThread
class is internally called.This
start()
method invokes therun()
method from theThread
class. However, therun()
method in theThread
class has no implementation—it is essentially an empty method.As a result, when you create a new thread using this constructor, it will invoke the empty
run()
method of theThread
class, and no actual task will be executed by that thread.
How to Create a Thread:
To create a thread, an object of the
Thread
class is instantiated.The
start()
method is called on this object to initiate the thread.Once the
start()
method is invoked, it internally calls therun()
method of theThread
class, which does nothing because it has no implementation.
Issue: Calling the
run()
Method fromThread
Class:When a thread is created using the zero-parameter
Thread()
constructor, it will call therun()
method from theThread
class. But, since thisrun()
method has no meaningful implementation, no task is executed.What we want instead is for the
run()
method from theMyRunnable
class to be invoked when the thread starts.
How to Resolve This Issue?:
To make the thread execute the
run()
method fromMyRunnable
, we need to pass an instance ofMyRunnable
to theThread
constructor.By doing this, we tell the
Thread
class to invoke therun()
method from theMyRunnable
class, which contains the actual logic for the task to be executed by the thread.
How Does Java Handle This?:
The solution is to pass a
Runnable
implementation to theThread
constructor.This way, the
Thread
class will invoke therun()
method of theRunnable
implementation passed to it instead of the emptyrun()
method in theThread
class.
Understanding Thread Constructors in Java
Constructor 1:
public java.lang.Thread()
This constructor creates a new thread but invokes the
run()
method from theThread
class, which has no implementation.If you use this constructor and call
start()
, it will call the emptyrun()
method inThread
class, and no task will be executed.
Constructor 2:
public java.lang.Thread(java.lang.Runnable)
This constructor allows you to pass a
Runnable
object to theThread
constructor.When
start()
is called, it will invoke therun()
method from theRunnable
object (e.g.,MyRunnable
), which contains the task you want to execute.
Code Example: Using the Thread()
Constructor
Code Explanation:
// MyRunnable class implements Runnable interface
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i < 5; i++) {
System.out.println("Child Thread");
}
}
}
public class Multithreading15 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable(); // Create an instance of MyRunnable
// Create a Thread object using the zero-parameter constructor
Thread t = new Thread(); // No Runnable passed
t.start(); // Start the thread, but run method of Thread class will be called
// Main thread task
for (int i = 1; i < 5; i++) {
System.out.println("Main Thread");
}
}
}
Output:
Main Thread
Main Thread
Main Thread
Main Thread
In this code:
Thread t = new Thread();
creates aThread
object using the constructor that doesn't take any parameters.When
t.start();
is called, therun()
method from theThread
class is executed, but it has no implementation, so it does nothing.The main thread runs its own task and prints "Main Thread" four times.
Fix: Using Runnable
Interface with Thread
To ensure that the run()
method from MyRunnable
is executed, we need to pass MyRunnable
to the Thread
constructor. Here's how you can correct the code:
public class Multithreading15 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable(); // Create an instance of MyRunnable
// Create a Thread object using the constructor that takes Runnable
Thread t = new Thread(r); // Pass MyRunnable as the task
t.start(); // Start the thread, which will execute MyRunnable's run()
// Main thread task
for (int i = 1; i < 5; i++) {
System.out.println("Main Thread");
}
}
}
Output (Fixed Code):
Main Thread
Main Thread
Main Thread
Main Thread
Child Thread
Child Thread
Child Thread
Child Thread
Now, both the main thread and the child thread print their respective messages.
The child thread prints "Child Thread" as defined in the
run()
method ofMyRunnable
.The main thread prints "Main Thread".
Key Takeaways:
Thread Constructor:
The zero-parameter constructor of the
Thread
class does not allow any task execution unless overridden.The task to be executed by the thread should be passed via
Runnable
when creating theThread
object.
Thread vs. Runnable:
Thread
class: It can be used directly to create a thread, but by default, itsrun()
method does nothing.Runnable
interface: It allows you to define a task (viarun()
method) that can be executed by a thread.
Proper Thread Creation:
- To create a thread that performs useful work, always pass a
Runnable
implementation (likeMyRunnable
) to theThread
constructor.
- To create a thread that performs useful work, always pass a
Code-3: Creating a Thread Using the Runnable Interface
Explanation:
In this code, we use the public java.lang.Thread(java.lang.Runnable)
constructor to create a thread using the Runnable
interface. Here's a step-by-step breakdown:
The
Runnable
Interface:The
Runnable
interface defines a single method,run()
, which contains the task that should be executed by a thread.The
run()
method has no return type and is intended to be overridden by a class that implementsRunnable
to specify the task for the thread.
Thread Creation Using
Runnable
:The
Thread
constructor that takes aRunnable
object as a parameter allows thestart()
method to be called from theThread
class.However, instead of invoking the empty
run()
method from theThread
class (like in the previous case with the zero-parameter constructor), this constructor invokes therun()
method from theMyRunnable
class.
How This Works:
MyRunnable
object is created first. This class implements theRunnable
interface and defines therun()
method, where the actual task for the thread is specified.A new
Thread
object is then created with theMyRunnable
object passed to it.t.start()
is called to start the thread. Thestart()
method in theThread
class registers the thread with the Thread Scheduler and triggers therun()
method in theMyRunnable
class.
Multithreading:
At this point, there are two threads running concurrently:
The main thread: It runs the main method and prints "Main Thread" four times.
The user-defined thread: This thread executes the
run()
method inMyRunnable
, printing "Child Thread" four times.
Since both threads are running simultaneously, context switching happens between them, meaning the CPU alternates between executing the main thread and the user-defined thread.
Code Explanation:
// MyRunnable implements Runnable interface
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i < 5; i++) {
System.out.println("Child Thread"); // Prints "Child Thread"
}
}
}
public class Multithreading16 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable(); // Line-1: Create MyRunnable object
// Create a new thread and pass MyRunnable to it
Thread t = new Thread(r); // Creates a new thread for MyRunnable
t.start(); // Start the user-defined thread
// Main thread executes this code
for (int i = 1; i < 5; i++) {
System.out.println("Main Thread"); // Prints "Main Thread"
}
}
}
Step-by-Step Execution:
Line 1: A new
MyRunnable
object is created.MyRunnable r = new MyRunnable();
This object contains the
run()
method that will execute the task for the user-defined thread.
Creating and Starting the Thread:
Thread t = new Thread(r);
A new
Thread
objectt
is created, passing theMyRunnable
object as a parameter.t.start();
starts the thread and calls therun()
method ofMyRunnable
.
Thread Execution:
The main thread (the one running the
main
method) continues its execution and prints "Main Thread" four times.Meanwhile, the user-defined thread starts executing the
run()
method ofMyRunnable
and prints "Child Thread" four times.Context switching happens here, where the CPU alternates between the two threads, allowing them to execute concurrently.
Output:
Main Thread
Child Thread
Main Thread
Main Thread
Main Thread
Child Thread
Child Thread
Child Thread
The output alternates between "Main Thread" and "Child Thread", demonstrating the concept of multithreading.
The main thread prints "Main Thread" four times, and the user-defined thread prints "Child Thread" four times.
Key Takeaways:
Runnable
Interface:- The
Runnable
interface provides a way to define the task to be executed by a thread by implementing therun()
method.
- The
Thread Constructor with
Runnable
:- By passing a
Runnable
object to theThread
constructor (public Thread(Runnable target)
), theThread
class invokes therun()
method of theRunnable
implementation when thestart()
method is called.
- By passing a
Multithreading:
In this example, two threads are executing concurrently: the main thread and the user-defined thread (created using the
Runnable
interface).Context switching occurs between the two threads, meaning the CPU alternates between executing the code from the main thread and the user-defined thread.
Using
start()
vs.run()
:The
start()
method inThread
class causes the thread to begin executing, and it invokes therun()
method where the actual task is defined.If you directly call the
run()
method, it will simply execute in the current thread (main thread), not creating a new thread.
Improvement Note:
To ensure that both threads work as expected, avoid calling run()
directly. Always use start()
to initiate a new thread and allow for concurrent execution.
Case Study: Thread Creation and Output Analysis
Given Code:
class MyRunnable implements Runnable {
@Override
public void run() {
// Task-2: This task is executed by the thread associated with MyRunnable
for (int i = 1; i < 5; i++) {
System.out.println("Child Thread");
}
}
}
public class Multithreading17 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(); // Thread t1 created without Runnable
Thread t2 = new Thread(r); // Thread t2 created with Runnable
t1.start(); // Starts t1, but it executes the run method of Thread class
for (int i = 1; i < 5; i++) {
System.out.println("Main Thread"); // Task-1: Executed by the main thread
}
}
}
Analysis:
In this case, two threads are involved:
t1
: Created using theThread
constructor without aRunnable
object.t2
: Created using theThread(Runnable)
constructor, passing theMyRunnable
object.
However, the code only calls start()
on t1
, not on t2
. Let's break down what happens when the program runs:
Thread t1:
Thread t1 = new Thread();
creates a thread without aRunnable
.When
t1.start()
is called, thestart()
method from theThread
class is invoked.Since
t1
was not provided with aRunnable
, it will invoke therun()
method defined in theThread
class itself.The
Thread.run
()
method has no implementation (empty), so no task is executed byt1
.
Main Thread:
- The main thread will execute the
for
loop and print "Main Thread" four times.
- The main thread will execute the
Thread t2:
Thread t2 = new Thread(r);
creates a thread withMyRunnable
.However,
t2.start()
is never called in this code, so therun()
method inMyRunnable
is never executed.Therefore,
Child Thread
is not printed byt2
.
How many threads?
2 threads will be created in this case:
Main thread: The default thread running the
main
method.Thread t1: A new thread created but it doesn't execute any task due to the empty
run()
method in theThread
class.
Since t2.start()
is not called, Thread t2 does not actually run.
Output:
Since only t1.start()
is invoked, and t1
does nothing (because the run()
method in Thread
is empty), the main thread will execute the for
loop and print "Main Thread" four times.
The output will be:
Main Thread
Main Thread
Main Thread
Main Thread
Summary:
Number of threads: 2 (main thread and
t1
)What happened?
t1
was started, but it doesn't do anything because it uses therun()
method from theThread
class (which is empty).t2
was created but not started, so it never executes.
Output: The main thread prints "Main Thread" four times.
Case Study: Thread Creation and Output Analysis (Case-2)
Given Code:
class MyRunnable implements Runnable {
@Override
public void run() {
// Task-2: This task is executed by the thread associated with MyRunnable
for (int i = 1; i < 5; i++) {
System.out.println("Child Thread");
}
}
}
public class Multithreading18 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(); // Thread t1 created without Runnable
Thread t2 = new Thread(r); // Thread t2 created with Runnable
t2.start(); // Starts t2, which executes the run method of MyRunnable
// Task-1: Executed by the main thread
for (int i = 1; i < 5; i++) {
System.out.println("Main Thread");
}
}
}
Analysis:
In this case, two threads are involved:
t1
: Created using theThread
constructor without aRunnable
object.t2
: Created using theThread(Runnable)
constructor, passing theMyRunnable
object.
The t2.start()
method is called, which starts t2
and invokes the run()
method of MyRunnable
.
Let's break down what happens when the program runs:
Thread t1:
Thread t1 = new Thread();
creates a thread without aRunnable
.t1.start()
is not called, sot1
does not perform any task.
Thread t2:
Thread t2 = new Thread(r);
creates a thread withMyRunnable
.When
t2.start()
is called, it invokes therun()
method ofMyRunnable
, printing "Child Thread" four times.
Main Thread:
- The main thread executes the
for
loop and prints "Main Thread" four times.
- The main thread executes the
How many threads?
2 threads will be created in this case:
Main thread: The default thread running the
main
method.Thread t2: A new thread created with
MyRunnable
that executes therun()
method fromMyRunnable
.
Since t1.start()
is not called, t1
does not execute.
Output:
The output will be as follows:
The main thread starts and prints "Main Thread" four times.
The user-defined thread (
t2
) starts aftert2.start()
is called, executing therun()
method ofMyRunnable
, printing "Child Thread" four times.
Since t2.start()
is called after the loop in the main thread, the exact order of output can vary due to thread scheduling. Typically, the output could be:
Main Thread
Main Thread
Main Thread
Main Thread
Child Thread
Child Thread
Child Thread
Child Thread
However, due to context switching and multithreading, the output can interleave in other ways. For example:
Main Thread
Child Thread
Main Thread
Child Thread
Main Thread
Child Thread
Main Thread
Child Thread
Summary:
Number of threads: 2 (main thread and
t2
).What happens?:
Main thread executes the
for
loop printing "Main Thread".Thread t2 executes the
run()
method fromMyRunnable
, printing "Child Thread".
Output: The main thread prints "Main Thread" four times, and the user-defined thread prints "Child Thread" four times. The exact order may vary depending on thread scheduling.
Case Study: Calling t1.run
()
Instead of t1.start()
Given Code:
class MyRunnable implements Runnable {
@Override
public void run() {
// Task-2: This task is executed by the thread associated with MyRunnable
for (int i = 1; i < 5; i++) {
System.out.println("Child Thread");
}
}
}
public class Multithreading19 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(); // t1 is created without Runnable
Thread t2 = new Thread(r); // t2 is created with MyRunnable
t1.run(); // Calling run() directly, instead of start()
// Task-1: Executed by the main thread
for (int i = 1; i < 5; i++) {
System.out.println("Main Thread");
}
}
}
Analysis:
In this case, we are calling the run()
method directly instead of start()
for t1
. Let's break down the behavior and expected output:
Thread
t1
:Thread t1 = new Thread();
creates aThread
object.t1.run
();
calls therun()
method directly instead of starting a new thread.Since
run()
is called directly, it behaves like a normal method call and does not create a new thread.It will simply execute the empty implementation of the
run()
method from theThread
class, which doesn't do anything.
Thread
t2
:Thread t2 = new Thread(r);
creates aThread
object with theMyRunnable
object passed as aRunnable
.However,
t2.start()
is never called, so therun()
method ofMyRunnable
is never invoked. The task inMyRunnable
is not executed in this case.
Main Thread:
The main thread executes the
for
loop and prints "Main Thread" four times.No new threads are created or executed.
How many threads?
In this case, only one thread (the main thread) will be active because:
t1.run
()
does not create a new thread (it simply callsrun()
as a normal method).t2.start()
is not called, sot2
will not execute therun()
method ofMyRunnable
.
Thus, only the main thread is responsible for the output.
Output:
The output will be as follows, as only the main thread is executing:
Main Thread
Main Thread
Main Thread
Main Thread
Explanation:
The main thread executes the
for
loop and prints "Main Thread" four times.The
run()
method oft1
does not get executed because it was called directly and it has no implementation in this case.The
run()
method ofMyRunnable
is never invoked becauset2.start()
is not called.
Summary:
Number of threads: 1 (main thread).
What happens?:
t1.run
()
is called directly, but it does not create a new thread. It simply calls therun()
method like any normal method.t2.start()
is not called, sot2
does not execute any task.The main thread prints "Main Thread" four times.
Case Study: Calling t2.run
()
Directly
Given Code:
class MyRunnable implements Runnable {
@Override
public void run() {
// Task-2: This task is executed by the thread associated with MyRunnable
for (int i = 1; i < 5; i++) {
System.out.println("Child Thread");
}
}
}
public class Multithreading20 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(); // t1 is created without Runnable
Thread t2 = new Thread(r); // t2 is created with MyRunnable
t2.run(); // Calling run() directly, instead of start()
// Task-1: Executed by the main thread
for (int i = 1; i < 5; i++) {
System.out.println("Main Thread");
}
}
}
Analysis:
In this case, t2.run
()
is called directly, instead of t2.start()
. This impacts how the threads behave, as run()
behaves like a normal method call, and start()
would have created a new thread.
Thread
t1
:Thread t1 = new Thread();
creates aThread
object, but no action is performed ont1
in this case.No thread is started for
t1
, so no output is produced byt1
.
Thread
t2
:Thread t2 = new Thread(r);
creates aThread
object withMyRunnable
.t2.run
()
is called directly (instead oft2.start()
), meaning therun()
method ofMyRunnable
is executed on the main thread as a normal method call, not on a separate thread.Since
run()
is executed directly, it behaves like a regular method call and does not create a new thread.
Main Thread:
- The main thread will also execute its
for
loop and print "Main Thread" four times.
- The main thread will also execute its
How many threads?
Only one thread is active here: the main thread.
t2.run
()
does not create a new thread, it just executes the code inMyRunnable
on the main thread.t1
is not used, so it does not impact the output.
Output:
The output will show "Child Thread" printed four times by the main thread, as
run()
is executed in the main thread.After that, the main thread will continue to execute and print "Main Thread" four times.
Child Thread
Child Thread
Child Thread
Child Thread
Main Thread
Main Thread
Main Thread
Main Thread
Explanation:
The main thread executes both the task in
MyRunnable
(becauserun()
is called directly) and the main thread's task (printing "Main Thread").No new threads are created because
t2.run
()
is just a normal method call, and not a thread start.Thus, the main thread is responsible for both tasks.
Summary:
Number of threads: 1 (main thread).
What happens?:
t2.run
()
executes therun()
method ofMyRunnable
directly on the main thread.The main thread prints "Child Thread" and "Main Thread".
Case Study: Calling r.start()
Given Code:
class MyRunnable implements Runnable {
@Override
public void run() {
// Task-2: This task is executed by the thread associated with MyRunnable
for (int i = 1; i < 5; i++) {
System.out.println("Child Thread");
}
}
}
public class Multithreading21 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(); // t1 is created without Runnable
Thread t2 = new Thread(r); // t2 is created with MyRunnable
r.start(); // Error: MyRunnable does not have a start() method
// Task-1: Executed by the main thread
for (int i = 1; i < 5; i++) {
System.out.println("Main Thread");
}
}
}
Analysis of the Code:
In this case, r.start()
is called, where r
is an object of type MyRunnable
. However, there is no start()
method in the MyRunnable
class. This leads to a compile-time error, because the start()
method is defined in the Thread
class, not the Runnable
interface or its implementing classes.
r.start()
:r
is an instance ofMyRunnable
, which implementsRunnable
.The
Runnable
interface only has therun()
method, not thestart()
method.The
start()
method is part of theThread
class and is used to begin a new thread, butstart()
can only be called on instances of theThread
class (or its subclasses).Since
r
is not aThread
object, callingstart()
onr
results in a compile-time error.
Thread t2 = new Thread(r);
:t2
is aThread
object that is constructed withr
(which is of typeMyRunnable
).In this case, the
Thread
objectt2
would be used to executerun()
(through theRunnable
interface). But, sincer.start()
is invoked onr
(which is aMyRunnable
object), the program will not reach this part due to the compilation error.
Error in the Code:
The code will fail to compile because of this line:
r.start(); // Error: MyRunnable does not have a start() method
The specific error message will be something like:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The method start() is undefined for the type MyRunnable
Key Points:
The
start()
method belongs to theThread
class, not theRunnable
interface or its implementing classes.You can only call
start()
on instances of theThread
class or its subclasses.The
Runnable
interface provides therun()
method, but not thestart()
method.To create and start a thread, you would use
new Thread(r).start()
, wherer
is aRunnable
instance.
Correct Usage:
To avoid the error and correctly implement multithreading, you should call start()
on the Thread
object, not the Runnable
object.
Here’s how you can fix the code:
public class Multithreading21 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(); // Thread t1 is not needed in this case
Thread t2 = new Thread(r); // t2 is created with MyRunnable
t2.start(); // Correct way to start the thread
// Task-1: Executed by the main thread
for (int i = 1; i < 5; i++) {
System.out.println("Main Thread");
}
}
}
Output after the correction:
Main Thread
Main Thread
Main Thread
Main Thread
Child Thread
Child Thread
Child Thread
Child Thread
Explanation:
Thread
t2
will execute therun()
method ofMyRunnable
, printing "Child Thread" four times.The main thread will execute its own loop, printing "Main Thread" four times.
Conclusion:
Number of threads: 2
Main thread: Executes the task in the main method.
User-defined thread (
t2
): Executes the task in therun()
method ofMyRunnable
.
What happens?:
If you call
r.start()
, it will result in a compile-time error because thestart()
method is not defined in theMyRunnable
class.To fix this, you should call
t2.start()
wheret2
is aThread
object.
Case Study: Calling r.run
()
Given Code:
class MyRunnable implements Runnable {
@Override
public void run() {
// Task-2: This task is executed by the thread associated with MyRunnable
for (int i = 1; i < 5; i++) {
System.out.println("Child Thread");
}
}
}
public class Multithreading22 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(); // t1 is created without Runnable
Thread t2 = new Thread(r); // t2 is created with MyRunnable
r.run(); // Calling run() directly
// Task-1: Executed by the main thread
for (int i = 1; i < 5; i++) {
System.out.println("Main Thread");
}
}
}
Analysis of the Code:
In Case-6, the code calls r.run
()
instead of r.start()
. This approach does not create a new thread, and it runs the run()
method of MyRunnable
directly in the main thread. Let's break it down step by step:
r.run
()
:run()
is a normal method in theMyRunnable
class.Calling
r.run
()
directly does not create a new thread. Instead, it executes therun()
method on the main thread just like any regular method.The
run()
method inMyRunnable
prints "Child Thread" four times.
Main Thread Execution:
- After
r.run
()
is completed, the main thread continues executing its own loop, printing "Main Thread" four times.
- After
Key Points:
No new thread is created in this case. The
run()
method ofMyRunnable
is executed just like a regular method, but it's executed on the main thread.This is different from calling
start()
on aThread
object, which would result in the creation of a new thread to execute therun()
method concurrently with the main thread.The output will be sequential because both the
run()
method ofMyRunnable
and the main thread’s loop are executed on the same thread (the main thread).
Number of Threads:
There is only 1 thread in this case: the main thread.
Even though
t2
is created, it is not used, andt1
is also unused. Only the main thread executes the tasks.
Output:
The output will be:
Child Thread
Child Thread
Child Thread
Child Thread
Main Thread
Main Thread
Main Thread
Main Thread
Explanation of Output:
r.run
()
prints "Child Thread" four times because therun()
method ofMyRunnable
is executed directly in the main thread.After that, the main thread continues to execute its own loop, printing "Main Thread" four times.
Conclusion:
Number of threads: 1 (the main thread)
Output:
Child Thread Child Thread Child Thread Child Thread Main Thread Main Thread Main Thread Main Thread
Reason: Calling
r.run
()
does not start a new thread. Therun()
method is executed on the main thread, followed by the main thread printing "Main Thread" four times.
Analysis of the Cases and Questions
Let's break down each case and answer the questions accordingly. We are specifically looking at how threads behave in each scenario.
Code Setup:
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i < 5; i++) {
System.out.println("Child Thread");
}
}
}
public class MultithreadingExample {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(); // t1: A thread without MyRunnable
Thread t2 = new Thread(r); // t2: A thread with MyRunnable
// Code with different cases
}
}
1. In which of the above cases a new Thread will be created which is responsible for the execution of MyRunnable's run()
method?
Case 1:
t1.start()
No new thread for MyRunnable's
run()
:t1
is created without aRunnable
, so it will not execute theMyRunnable
'srun()
method. It will call therun()
method of theThread
class, but this method is empty, so nothing will happen. No new thread forMyRunnable
.
Case 2:
t2.start()
A new thread for MyRunnable's
run()
:When
t2.start()
is called, a new thread is created and started. Thestart()
method of theThread
class will internally invokeMyRunnable
'srun()
method in a new thread. So, a new thread forMyRunnable.run
()
will be created.
Case 3:
t2.run
()
No new thread for MyRunnable's
run()
:t2.run
()
does not start a new thread. It simply calls therun()
method directly in the main thread. So, no new thread is created, andMyRunnable.run
()
will execute in the main thread.
Case 4:
t1.run
()
No new thread for MyRunnable's
run()
:Like
t2.run
()
, callingt1.run
()
also does not create a new thread. It simply invokes therun()
method ofThread
, which is empty. Hence, no thread is created forMyRunnable.run
()
.
Case 5:
r.start()
Compilation Error:
This case results in a compile-time error because
r
is an instance ofMyRunnable
and does not have astart()
method. Thestart()
method belongs to theThread
class. This would result in a compilation error and hence will not create a new thread.
Case 6:
r.run
()
No new thread for MyRunnable's
run()
:r.run
()
calls therun()
method directly within the main thread. It does not create a new thread, it simply executes therun()
method as a normal method call in the main thread.
Answer to Question 1:
A new thread will be created and responsible for the execution of MyRunnable's run()
method only in Case 2: t2.start()
.
2. In which of the above cases a new Thread will be created?
Case 1:
t1.start()
No new thread:
t1
is created without aRunnable
and calls therun()
method of theThread
class, which is empty. So no new thread is created.
Case 2:
t2.start()
New thread is created:
t2.start()
creates a new thread and invokesMyRunnable.run
()
in that thread.
Case 3:
t2.run
()
No new thread:
t2.run
()
simply calls therun()
method on the main thread, not creating a new thread.
Case 4:
t1.run
()
No new thread:
t1.run
()
simply callsrun()
in the main thread (without creating any new thread).
Case 5:
r.start()
Compilation Error:
The
start()
method is not defined in theMyRunnable
class, so this case results in a compile-time error, and no thread is created.
Case 6:
r.run
()
No new thread:
Calling
r.run
()
simply invokes therun()
method on the main thread, without creating any new threads.
Answer to Question 2:
A new thread will be created only in Case 2: t2.start()
.
3. In which of the above cases MyRunnable's run()
method will be executed?
Case 1:
t1.start()
MyRunnable's
run()
will not be executed:t1
does not have aRunnable
, so it does not invokeMyRunnable.run
()
. Therun()
method ofThread
(which is empty) is invoked instead.
Case 2:
t2.start()
MyRunnable's
run()
will be executed:t2.start()
creates a new thread and therun()
method ofMyRunnable
is invoked in the new thread.
Case 3:
t2.run
()
MyRunnable's
run()
will be executed:Calling
t2.run
()
invokesMyRunnable.run
()
directly, but it executes in the main thread instead of a new thread.
Case 4:
t1.run
()
MyRunnable's
run()
will not be executed:t1.run
()
invokesrun()
ofThread
, which is empty. Therefore, MyRunnable'srun()
is not executed.
Case 5:
r.start()
Compile-time error:
r
is an instance ofMyRunnable
, and it does not have astart()
method, so this case results in a compile-time error. MyRunnable'srun()
will not be executed.
Case 6:
r.run
()
MyRunnable's
run()
will be executed:Calling
r.run
()
directly callsMyRunnable.run
()
in the main thread, not in a new thread.
Answer to Question 3:
MyRunnable.run() will be executed in the following cases:
Case 2:
t2.start()
(in a new thread)Case 3:
t2.run
()
(in the main thread)Case 6:
r.run
()
(in the main thread)
Summary of Answers:
Which case creates a new thread responsible for executing
MyRunnable.run
()
?- Case 2:
t2.start()
- Case 2:
Which case creates a new thread?
- Case 2:
t2.start()
- Case 2:
Which cases execute
MyRunnable.run
()
?
Different Approaches for Creating a Thread in Java
Java provides two main ways to create and run threads: extending the Thread
class and implementing the Runnable
interface. Let's explore both approaches, their advantages and disadvantages, and why implementing the Runnable
interface is generally considered the best approach.
A. Extending the Thread
Class (Not Recommended)
This approach involves creating a new class that extends the Thread
class and overriding the run()
method to define the task that the thread will execute.
Code Example:
// Extending the Thread class
class MyThread extends Thread {
@Override
public void run() {
// Task to be executed by the thread
for (int i = 1; i <= 5; i++) {
System.out.println("Child Thread");
}
}
}
public class Main {
public static void main(String[] args) {
MyThread t = new MyThread(); // Creating an instance of the custom thread
t.start(); // Starting the thread
}
}
Issues with Extending the Thread
Class:
No Inheritance from Other Classes:
Only one class can be inherited in Java, and if we extend the
Thread
class, our custom class cannot extend any other class because Java does not support multiple inheritance of classes.Inheritance loss: If our class extends
Thread
, it loses the ability to inherit from other classes, which can be restrictive in some situations.
Limited Reusability:
Extending
Thread
directly limits the reusability of the class. It becomes tightly coupled with theThread
class, and you can't extend other classes if needed.Not flexible: If the class already extends another class, it is not possible to extend
Thread
because Java allows only single inheritance.
Why this Approach is Not Ideal:
The thread-related functionality is tightly coupled to the class, meaning that the class cannot be reused for other purposes (like being part of a larger class hierarchy).
Performance and memory considerations: This approach may lead to suboptimal memory usage and performance because each thread object has its own state and methods, even if they are not all needed.
B. Implementing the Runnable
Interface (Recommended Approach)
In this approach, we create a class that implements the Runnable
interface and overrides the run()
method to define the task that the thread will execute. A Thread
object is then created, passing the Runnable
instance to it.
Code Example:
// Implementing the Runnable interface
class MyRunnable implements Runnable {
@Override
public void run() {
// Task to be executed by the thread
for (int i = 1; i <= 5; i++) {
System.out.println("Child Thread");
}
}
}
public class Main {
public static void main(String[] args) {
MyRunnable r = new MyRunnable(); // Creating a Runnable object
Thread t = new Thread(r); // Creating a thread object and passing Runnable
t.start(); // Starting the thread
}
}
Advantages of Implementing Runnable
:
Allows Inheritance from Other Classes:
The
Runnable
interface allows you to implement multiple interfaces and extend other classes, which is not possible with theThread
class (since Java does not support multiple inheritance for classes).This makes your class more flexible and reusable.
Better Performance and Memory Usage:
By separating the task (
Runnable
) from theThread
object, this approach leads to better performance and memory management. TheRunnable
interface is lightweight compared to extending theThread
class.You can reuse the same
Runnable
object across multipleThread
objects, which reduces memory overhead.
Separation of Concerns:
In this approach, task and thread management are separate. The
Runnable
defines the task, and theThread
class is responsible for managing the thread execution. This separation improves code modularity and maintainability.You can have a
Runnable
task that can be used with different thread objects.
Better in Real-World Applications:
- In real-world applications, you often need to manage more complex tasks, and using
Runnable
makes it easier to handle such tasks. For example, when working with thread pools,Runnable
is preferred because it decouples the task definition from the thread management logic.
- In real-world applications, you often need to manage more complex tasks, and using
Why This Approach is Recommended:
Reusability: You can reuse the same
Runnable
class with different thread objects, and your class can still extend other classes.Performance: The approach results in better memory management and thread management.
Separation of concerns: The
Runnable
interface handles the task, and theThread
class manages the thread execution.
Conclusion:
Best Approach: Implementing the
Runnable
interface is the recommended approach for creating threads in Java because it allows your class to implement multiple interfaces and extend other classes, making it more flexible and reusable.Why not extend
Thread
?: ExtendingThread
leads to tight coupling with theThread
class, preventing inheritance from other classes and limiting the reusability of the class.
Real-World Usage: In most real-world multi-threaded applications, implementing Runnable
is preferred, especially when tasks are independent and should be executed concurrently.
Constructors Available in the Thread
Class
The Thread
class in Java has several constructors, which allow you to create a thread in different ways depending on how you want to initialize it. Below are the various constructors available, as you would find by running javap java.lang.Thread
in the command prompt:
1. Thread t = new Thread()
This constructor creates a new thread without associating it with any task or a specific name. This is the simplest constructor that just creates a thread.
Usage: It creates a thread with a default name such as "Thread-0", "Thread-1", etc.
Example:
Thread t = new Thread(); t.start();
2. Thread t = new Thread(Runnable r)
This constructor creates a new thread that will execute the task defined in the Runnable
interface. The Runnable
task is passed to the thread, and when the thread is started, it will execute the run()
method of the Runnable
.
Usage: This is the constructor you'd use when you want to create a thread and pass a
Runnable
task to it.Example:
Runnable task = new Runnable() { public void run() { System.out.println("Task is running in a new thread!"); } }; Thread t = new Thread(task); t.start();
3. Thread t = new Thread(String name)
This constructor creates a new thread and sets its name. This is useful when you want to specify a name for the thread for easier identification during debugging or logging.
Usage: You use this constructor when you want to create a thread with a specific name, rather than the default one like "Thread-0".
Example:
Thread t = new Thread("MyCustomThread"); t.start();
4. Thread t = new Thread(Runnable r, String name)
This constructor creates a new thread with a specified Runnable
task and a custom name. This is one of the most commonly used constructors, as it combines both functionality: the task to be executed and a name for the thread.
Usage: This is often used in real-world applications when you need both a specific task and a custom thread name.
Example:
Runnable task = new Runnable() { public void run() { System.out.println("Task is running in a new thread with a custom name!"); } }; Thread t = new Thread(task, "CustomThreadName"); t.start();
5. Thread t = new Thread(ThreadGroup g, String name)
This constructor creates a new thread and associates it with a specific ThreadGroup
. A ThreadGroup
is used to group multiple threads for management purposes.
Usage: This is used when you want to create a thread that belongs to a particular
ThreadGroup
.Example:
ThreadGroup group = new ThreadGroup("MyThreadGroup"); Thread t = new Thread(group, "GroupThread"); t.start();
6. Thread t = new Thread(ThreadGroup g, Runnable r)
This constructor creates a new thread that will execute the task defined in the Runnable
interface and belongs to a specified ThreadGroup
.
Usage: Used when you need both a
Runnable
task and a specificThreadGroup
.Example:
ThreadGroup group = new ThreadGroup("Group1"); Runnable task = new Runnable() { public void run() { System.out.println("Task in specific thread group"); } }; Thread t = new Thread(group, task); t.start();
7. Thread t = new Thread(ThreadGroup g, Runnable r, String name)
This constructor creates a new thread that executes a Runnable
task, belongs to a ThreadGroup
, and has a custom thread name.
Usage: This is useful when you need all three: a
Runnable
task, a specificThreadGroup
, and a custom name for the thread.Example:
ThreadGroup group = new ThreadGroup("Group2"); Runnable task = new Runnable() { public void run() { System.out.println("Task with custom name in specific group"); } }; Thread t = new Thread(group, task, "CustomThreadInGroup"); t.start();
8. Thread t = new Thread(ThreadGroup g, Runnable r, String name, long stackSize)
This constructor creates a new thread with a specified Runnable
, ThreadGroup
, custom name, and a stack size. The stackSize
specifies the size of the stack for the new thread, which is useful for applications that require fine control over memory usage.
Usage: Used when you need all three components and also need to specify the stack size for the thread.
Example:
ThreadGroup group = new ThreadGroup("Group3"); Runnable task = new Runnable() { public void run() { System.out.println("Task with custom name, group, and stack size"); } }; long stackSize = 1024 * 1024; // 1MB stack size Thread t = new Thread(group, task, "CustomThreadWithStackSize", stackSize); t.start();
Summary of Common Constructors:
In real-time coding, the top 4 constructors are most commonly used. They provide the flexibility to specify both the task and the thread's name, while still allowing you to use the Runnable
interface, which is a better practice for multi-threaded applications. Here's a quick rundown:
Thread()
— Default constructor, rarely used in practice.Thread(Runnable r)
— Used to pass aRunnable
task to the thread.Thread(String name)
— Used to give the thread a custom name.Thread(Runnable r, String name)
— Used to pass aRunnable
task and a custom name to the thread, which is the most common constructor in real-world applications.
The other constructors that involve ThreadGroup
and stackSize
are typically used in more specialized situations where you need better control over thread grouping and memory management.
Best Practices:
Use
Runnable
withThread
: PreferRunnable
for defining the task that a thread will execute, because it allows for better design and reusability.Custom thread names: Set a custom thread name for easier debugging, especially when multiple threads are involved.
ThreadGroups: Use
ThreadGroup
if you need to manage multiple threads together. However, thread groups are less commonly used in modern Java applications.
How Many Threads are Created and What is the Output?
In this case, two threads are created:
Main Thread: The thread in which the
main()
method runs.User-Defined Thread: A custom thread defined by extending the
Thread
class, which will execute the overriddenrun()
method inMyThread
.
// Defining the MyThread class that extends the Thread class
class MyThread extends Thread {
@Override
public void run() {
// Task in the run method of MyThread
for (int i = 1; i < 5; i++) {
System.out.println("run method is called");
}
}
}
public class Multithreading26 {
public static void main(String[] args) {
// Creating an instance of MyThread
MyThread t = new MyThread();
// Creating an instance of Thread
Thread t1 = new Thread();
// Starting the t1 thread
t1.start(); // Starts a new thread (Main Thread will execute run() method, but it has no output)
// Task in main thread
for (int i = 1; i < 5; i++) {
System.out.println("Main Thread");
}
}
}
Explanation of Code:
Step 1: The
MyThread
class extends theThread
class and overrides therun()
method. When thestart()
method is called on an instance ofMyThread
, the thread begins execution, which internally invokes therun()
method.Step 2: In the
main()
method:A new instance of
MyThread
is created, but it is not started immediately.The
Thread
class'sstart()
method is called ont1
, which starts the Main Thread execution and also triggers the run() method of theThread
class (although it's empty, so it doesn't print anything).
Step 3: The Main Thread continues executing the
main()
method, and it prints "Main Thread" four times.
Threads Created:
Main Thread: This thread is already running when the
main()
method starts. It prints"Main Thread"
four times.User-Defined Thread (
MyThread
): This is the thread that starts whent1.start()
is called. This thread will run therun()
method ofMyThread
and print"run method is called"
four times.
Important Note: The output will not strictly follow the order of execution of threads, as Java thread scheduling is handled by the operating system's thread scheduler. Hence, the output may interleave between the "Main Thread" and "run method is called" messages. The order may vary each time you run the program.
Expected Output (One Possible Order):
Main Thread
Main Thread
Main Thread
Main Thread
run method is called
run method is called
run method is called
run method is called
Detailed Breakdown of Execution Flow:
Main Thread starts executing from the
main()
method.A new instance of
MyThread
is created but not started yet.t1.start()
is called, which:Invokes the
start()
method of theThread
class.Registers
t1
with the Thread Scheduler and starts a new thread (the Main Thread).
Both the Main Thread and the User-Defined Thread (
MyThread
) are running concurrently.The Main Thread prints "Main Thread" 4 times.
The User-Defined Thread (
MyThread
) prints "run method is called" 4 times.
Since the threads are running concurrently, their output may be interleaved in different ways each time you run the program.
Here's the combined code for defining a thread using an alternative approach, where a class implements the Runnable
interface, and the Runnable
object is passed to the Thread
constructor:
Code Explanation:
Runnable
Interface:- The
Runnable
interface has a single abstract methodrun()
that represents the task to be executed by a thread.
- The
Thread
Class:- The
Thread
class can be passed an object that implements theRunnable
interface, and when we callstart()
on theThread
object, it will invoke therun()
method of theRunnable
object.
- The
MyThread
Class:MyThread
extendsThread
, and therun()
method is overridden to define the task that this thread will perform (in this case, it just prints "run method is called").
In the
Multithreading27
Class:We create a
MyThread
objectt
, then pass this object as a parameter to theThread
constructor. After that, we start the thread usingt1.start()
, which will invoke therun()
method in theMyThread
class.Meanwhile, the
Main Thread
executes the task in the main method, which prints "Main Thread".
Code:
// Runnable interface
public interface Runnable {
public abstract void run();
}
// Thread class (pre-defined class in Java)
public class Thread implements Runnable {
public void start() {
// Register Thread with ThreadScheduler
// Perform other low-level activities
// Invoke run()
}
public void run() {
// Empty implementation
}
}
// MyThread class that extends Thread and implements Runnable
class MyThread extends Thread {
@Override
public void run() {
// Task in the run method of MyThread
System.out.println("run method is called");
}
}
public class Multithreading27 {
public static void main(String[] args) {
// Creating an instance of MyThread
MyThread t = new MyThread();
// Creating a new Thread object and passing MyThread instance as the Runnable task
Thread t1 = new Thread(t);
// Starting the thread (this invokes the run() method of MyThread)
t1.start();
// Task in the main thread
System.out.println("Main Thread");
}
}
Explanation:
MyThread
Class:MyThread
extends theThread
class, which already implements theRunnable
interface. This allowsMyThread
to run as a separate thread by overriding therun()
method.
Thread t1 = new Thread(t)
:- Here, the
MyThread
objectt
is passed to theThread
constructor as theRunnable
task. TheThread
objectt1
is responsible for starting theMyThread
task in a separate thread by callingt1.start()
, which will invoke therun()
method ofMyThread
.
- Here, the
t1.start()
:- This starts the
Thread
, and therun()
method ofMyThread
is invoked in a separate thread.
- This starts the
Output:
- The main thread will print "Main Thread", and the
MyThread
thread will print "run method is called".
- The main thread will print "Main Thread", and the
Expected Output:
Main Thread
run method is called
In this case, since threads execute concurrently, the order may vary between runs, but typically, the Main Thread
will print first, followed by the output of the MyThread
thread.
Key Takeaway:
- The approach of passing a
Runnable
object to theThread
constructor allows us to separate the task from the thread itself, giving more flexibility in terms of multiple tasks and thread reuse. This is often considered a better practice over directly extending theThread
class.
Explanation of Thread Naming in Java:
In Java, each thread has a name, and you can get or set the name of a thread. By default, threads are named automatically (e.g., Thread-0
, Thread-1
, etc.), but you can change the name of a thread if needed. Let's look at examples of how to get the name of the main thread and child threads using Thread.currentThread().getName()
and the getName()
method.
Example 1: Getting the Name of the Main Thread and Child Thread
// Thread class is a part of Java, no need to write it from scratch.
class MyThread extends Thread {
// This class extends Thread, so the run() method is overridden here
@Override
public void run() {
// This will get the name of the thread that runs this method
String name = Thread.currentThread().getName();
System.out.println("run() is executed by - " + name);
}
}
public class Multithreading28 {
public static void main(String[] args) {
// Getting the name of the main thread (the thread that runs the main method)
String name = Thread.currentThread().getName();
System.out.println("Main method is executed by - " + name);
// Creating a thread object
MyThread t = new MyThread();
// Printing the default name of the thread object
System.out.println(t); // Outputs something like: Thread[Thread-0,5,main]
// Starting the thread
t.start();
// Getting the name of the newly created child thread (Thread-0 by default)
System.out.println("Name of child thread is: " + t.getName()); // Outputs: Thread-0
}
}
Explanation:
Thread.currentThread().getName()
:- This method gets the name of the thread that is currently executing the code. In the case of
main
, it refers to the main thread that is running themain
method.
- This method gets the name of the thread that is currently executing the code. In the case of
t.getName()
:- This method returns the name of the thread
t
. SinceMyThread
extendsThread
, it is a child thread. Whenstart()
is called ont
, it creates a new thread with a default name (e.g.,Thread-0
).
- This method returns the name of the thread
Output:
Main method is executed by-main
tells you that themain
method is executed by themain
thread.Thread[Thread-0,5,main]
gives details about theThread-0
(name, priority, and thread group).Name of child thread is: Thread-0
confirms the name of the child thread (Thread-0
), which is created whent.start()
is called.
Expected Output:
Main method is executed by - main
Thread[Thread-0,5,main] // Default name, priority, and group
Name of child thread is: Thread-0
Example 2: Using run()
Method in a Custom Thread
class MyThread extends Thread {
@Override
public void run() {
// Get the name of the thread executing the run method
String name = Thread.currentThread().getName();
System.out.println("run() is executed by - " + name);
}
}
public class Multithreading28 {
public static void main(String[] args) {
// Getting the name of the main thread executing the main method
String name = Thread.currentThread().getName();
System.out.println("Main method is executed by - " + name);
// Creating an instance of MyThread
MyThread t = new MyThread();
// Starting the thread
t.start();
}
}
Explanation:
In this example,
MyThread
extends theThread
class, and we override therun()
method. Inside therun()
method,Thread.currentThread().getName()
is used to get the name of the thread that is executing therun()
method.We print the name of the main thread in the
main()
method and the name of the thread executingrun()
insideMyThread
.Output:
Main method is executed by-main
indicates that themain
method is executed by themain
thread.run() is executed by - Thread-0
shows the name of the child thread that is executing therun()
method (in this case, it'sThread-0
).
Expected Output:
Main method is executed by - main
run() is executed by - Thread-0
Key Takeaways:
The
Thread.currentThread().getName()
method gives the name of the thread that is executing the current block of code.The
Thread.getName()
method gives the name of a specific thread object, such as the main thread or a child thread.By default, when you create a new thread (using
new Thread()
or extendingThread
), Java assigns the thread a default name likeThread-0
,Thread-1
, etc., which can be accessed and modified usinggetName()
andsetName()
methods.
Setting and Changing Thread Names in Java
In Java, you can set and change the names of threads using the setName()
method. By default, each thread has a name like Thread-0
, Thread-1
, etc., but you can customize it to make your program more readable.
Let's go through examples where the name of the main thread and child threads is set and changed dynamically.
Example 3: Changing the Name of the Main Thread
class MyThread extends Thread {
@Override
public void run() {
// Running code for child thread (not changing name here)
}
}
public class Multithreading29 {
public static void main(String[] args) {
// Get the name of the main thread
String name = Thread.currentThread().getName();
System.out.println("Main method is executed by - " + name);
// Creating a new thread
MyThread t = new MyThread();
t.start(); // Starting the child thread
// Changing the name of the main thread
Thread.currentThread().setName("My thread");
System.out.println("Name of main thread changed to: " + Thread.currentThread().getName());
}
}
Explanation:
Thread.currentThread().getName()
: This method returns the name of the current thread, which, in this case, is themain
thread when running themain()
method.Thread.currentThread().setName("My thread")
: Changes the name of the current thread (main
thread in this case) to"My thread"
.Output:
- The name of the main thread is initially
"main"
, but it is changed to"My thread"
after callingsetName()
.
- The name of the main thread is initially
Expected Output:
Main method is executed by - main
Name of main thread changed to: My thread
Example 4: Exception in Main Thread
class MyThread extends Thread {
@Override
public void run() {
// Running code for child thread
}
}
public class Multithreading29 {
public static void main(String[] args) {
// Getting the name of the main thread
String name = Thread.currentThread().getName();
System.out.println("Main method is executed by - " + name);
// Creating the thread
MyThread t = new MyThread();
// Simulating an exception in the main thread
System.out.println(10 / 0); // This will throw ArithmeticException
}
}
Explanation:
The code simulates an exception (
ArithmeticException
) by attempting to divide by zero in the main thread.This demonstrates that the exception is thrown in the
main
thread, which will be displayed along with the thread's name (main
).Output:
- The exception occurs in the
main
thread, which is visible in the exception message.
- The exception occurs in the
Expected Output:
Main method is executed by - main
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Multithreading29.main(Multithreading29.java:14)
Example 5: Changing the Name of the Main Thread After Exception
class MyThread extends Thread {
@Override
public void run() {
// Running code for child thread
}
}
public class Multithreading29 {
public static void main(String[] args) {
// Getting the name of the main thread
String name = Thread.currentThread().getName();
System.out.println("Main method is executed by - " + name);
// Changing the name of the main thread before exception
Thread.currentThread().setName("ashish");
// Simulating an exception in the main thread
System.out.println(10 / 0); // This will throw ArithmeticException
}
}
Explanation:
Before the exception occurs, the name of the main thread is changed from
"main"
to"ashish"
usingThread.currentThread().setName("ashish")
.When the exception occurs (
ArithmeticException
), the name of the thread that caused the exception is displayed as"ashish"
, not"main"
.
Expected Output:
Main method is executed by - main
Exception in thread "ashish" java.lang.ArithmeticException: / by zero
at Multithreading29.main(Multithreading29.java:15)
Example 6: Changing the Name of Two Threads (Main and Child Thread)
class MyThread extends Thread {
@Override
public void run() {
// Changing the name of the child thread inside the run method
Thread.currentThread().setName("second thread");
System.out.println("Child thread name changed to " + Thread.currentThread().getName());
}
}
public class Multithreading29 {
public static void main(String[] args) {
// Creating and starting the child thread
MyThread t = new MyThread();
t.start();
// Changing the name of the main thread
Thread.currentThread().setName("First thread");
System.out.println("Main method changed to " + Thread.currentThread().getName());
}
}
Explanation:
Child Thread: Inside the
run()
method ofMyThread
, the name of the child thread is changed usingThread.currentThread().setName("second thread")
.Main Thread: The name of the main thread is changed to
"First thread"
usingThread.currentThread().setName("First thread")
.The output shows that both the main thread and the child thread have had their names changed.
Expected Output:
Main method changed to First thread
Child thread name changed to second thread
Key Takeaways:
setName()
: You can change the name of the current thread usingThread.currentThread().setName()
. This is useful for debugging or identifying threads in a multithreaded environment.Thread Names: By default, Java names threads automatically (e.g.,
Thread-0
,Thread-1
, etc.), but you can change these names for better clarity.Thread Names in Output: When exceptions occur, the thread name is shown as part of the exception message, which can be helpful for tracking where the exception occurred in a multi-threaded environment.
Conclusion: Multithreading in Java: Thread Creation, Naming, and Handling
In this discussion, we explored various concepts and techniques related to multithreading in Java, focusing on thread creation, managing thread names, and best practices for using threads effectively. Let's summarize the key points from the top:
1. Thread Creation: Extending Thread
vs Implementing Runnable
Extending
Thread
:The traditional way of creating threads involves subclassing the
Thread
class and overriding therun()
method. However, this approach has limitations such as:No inheritance: Once you extend
Thread
, your class can no longer extend any other class, limiting flexibility.Not a recommended approach in real-world applications, as it limits the ability to use inheritance effectively.
Implementing
Runnable
:A better approach is to implement the
Runnable
interface. This allows for:Inheritance flexibility: Your class can still extend other classes while implementing
Runnable
.Code reuse: The
Runnable
interface defines therun()
method, which can be passed to aThread
instance for execution.Scalability: In more complex systems, it's more common to use this approach because it supports better resource management and cleaner code.
2. Thread Constructors
The
Thread
class in Java has multiple constructors that allow for flexibility in creating threads:No-argument constructor (
Thread t = new Thread()
).Runnable constructor (
Thread t = new Thread(Runnable r)
).Named thread constructors (
Thread t = new Thread(String name)
).Thread with group and stack size for advanced use cases.
In real-time applications, the most common constructors are those that take a Runnable
object or allow naming the thread for easier debugging.
3. Thread Execution Process
When creating a new thread, the
start()
method must be called to invoke therun()
method.Important to note: The
run()
method is where the code for the thread’s task is defined. By default, if therun()
method is not overridden, it has an empty implementation.The
start()
method registers the thread with the Thread Scheduler, which decides when the thread should be executed.
4. Thread Naming and Accessing Thread Information
Threads in Java are given default names such as
"Thread-0"
,"Thread-1"
, etc., but you can change these names using thesetName()
method for better clarity, especially during debugging.Accessing the name of the current thread can be done using
Thread.currentThread().getName()
, which is useful for identifying which thread is executing a certain block of code.- Example:
String name = Thread.currentThread().getName();
provides the name of the current thread, which can be printed for debugging.
- Example:
5. Changing Thread Names Dynamically
The thread name can be set dynamically using
Thread.currentThread().setName()
, allowing you to track and identify threads more clearly in complex multithreaded programs.Example 1: Changing the main thread’s name.
Example 2: Changing the name of a child thread inside the
run()
method.This becomes especially useful when debugging multithreaded applications where you need to keep track of thread execution.
6. Handling Exceptions in Threads
As we saw in Example-4 and Example-6, exceptions thrown in the main thread or in child threads are visible with the thread's name. This helps in tracing where the exception occurred.
Handling exceptions within threads should be done carefully to prevent one thread's exception from affecting other threads in the application.
7. Conclusion and Best Practices
Recommended Thread Creation Approach: Implementing the
Runnable
interface is generally the preferred approach, as it allows for cleaner, more maintainable, and flexible code.Thread Naming: Always name your threads appropriately, especially in multithreaded environments, to make debugging easier. You can set the thread name dynamically using
setName()
.Handling Threads and Exceptions: Ensure that exceptions are caught and managed properly in each thread to avoid unexpected crashes and to provide useful debug information.
Performance Consideration: Threads should be used carefully, as improper use can lead to performance bottlenecks, race conditions, or excessive context switching. Always analyze your thread usage in relation to the system's resource constraints.
Overall Takeaway
Java's multithreading capabilities offer great power and flexibility, but they must be used wisely. Creating threads using Runnable
interfaces, managing thread names, and carefully handling exceptions are key techniques to ensure that your multithreaded programs are efficient, maintainable, and easy to debug. By understanding these concepts, you can implement complex multithreaded solutions that perform well under real-world conditions.