Table of contents
- Key Topics Covered:
- 1.Priority of Threads in Java
- Default Thread Priority
- Example 1: Default Priority of Threads
- Key Points on Thread Priority
- 2.Setting Priority of Threads in Java
- How to Set Thread Priority?
- Thread Priority Ranges
- Visual Representation of Thread Priority Allocation
- Example 2: Setting Thread Priority
- Execution Flow:
- Key Observations:
- Summary of Thread Priority Management
- Example 3: Invalid Priority Value
- Example 4: Setting Priority After Starting the Thread
- Preventing Threads from Execution Using yield(), sleep(), and join()
- 4. Methods to Prevent (Stop) Thread Execution
- Thread Lifecycle Overview
- 1. yield() Method
- 2. join() Method
- Overview
- Prototype of join()
- Explanation Through a Scenario
- Key Points
- Thread Waiting Scenarios
- Code Example: Using join()
- Output
- Explanation
- Interruption Scenario
- Summary
- Scenario-3: Marriage Planning with Dependent Actions
- Conceptual Flow
- Updated Code with Commented Sections
- Output
- Critical Notes Addressed
- Visualization
- Key Learnings
- Case-2 Explanation: Using public final void join(long ms) Method
- Concept Explanation
- Code with Explanation and Numbering
- Explanation of Execution
- Output:
- Key Observations
- Behavior Comparison
- Key Notes
- Conclusion
- Case-3 Explanation: Using public final void join(long ms, int ns) Method
- Concept Explanation
- Code with Explanation and Numbering
- Explanation of Execution
- Output:
- Key Observations
- Conclusion
- Key Notes:
- Case-4 Explanation: Child Thread Waiting for Parent Thread
- Concept Explanation
- Code Explanation with Numbering
- Explanation of Execution Flow
- Output
- Key Observations
- Conclusion
- Important Notes:
- Case-5: Deadlock Caused by join() Method
- Concept Explanation
- Code Explanation with Numbering
- Explanation of Execution Flow
- Output for Example-1
- Example-2: Deadlock Between Parent and Child Threads
- Code Explanation for Example-2
- Explanation of Execution Flow for Example-2
- Output for Example-2
- Key Observations
- Conclusion
- Life of a Thread When join() Method is Called
- Thread Lifecycle with join()
- Possibilities of Thread Entering Waiting State with join()
- Thread Movement Between States
- Example of join() in Action
- Explanation of the Example:
- Key Scenarios When Thread Moves from Waiting to Runnable
- Sleep Example with join()
- Conclusion
- Understanding the sleep() Method in Java
- When to Use sleep()
- Signatures of the sleep() Method
- Important Points to Note About sleep()
- Thread Flow with sleep()
- Example 1: Using sleep() in a Sequence
- Example 2: Using sleep() in a Loop
- Thread Lifecycle with sleep()
- Conclusion
- Life Cycle of a Thread When sleep() Method is Called
- 1. Transition to Sleeping State
- 2. Signatures of sleep() Method
- 3. Transition from Sleeping State to Ready/Runnable State
- Thread Lifecycle with sleep()
- Visual Representation
- Conclusion
- Conclusion for Chapter 40: Multithreading in Java (Part 3)
- Other Series:
In this chapter, we dive deep into key aspects of multithreading in Java, focusing on thread priorities, thread synchronization using join()
, and managing thread states with the sleep()
method. We will explore multiple practical scenarios and edge cases that illustrate how Java handles thread management, coordination, and execution.
Key Topics Covered:
Thread Priorities:
Understand how Java assigns priorities to threads and how the thread scheduler uses these priorities to decide which thread gets CPU time first.
Learn about setting thread priorities using
Thread.setPriority()
, and the effects of different priority levels on thread execution.
The
join()
Method:Explore how the
join()
method is used to make one thread wait for another to finish before proceeding.We will cover several use cases, demonstrating both correct usage and problematic cases like deadlocks when threads attempt to
join()
each other in improper sequences.
Thread Lifecycle with
sleep()
:Learn about the lifecycle of a thread when invoking the
sleep()
method. This allows a thread to pause its execution for a specific time, entering a sleeping state and then transitioning back to ready/runnable state after the sleep period ends or if interrupted.We will look at practical examples like simulating delays in a timer, managing timed tasks, and understanding the nuances of handling
InterruptedException
.
Real-World Scenarios:
- Multiple examples are provided to understand how to use
join()
,sleep()
, and thread priorities in practical scenarios, including managing tasks in parallel, preventing thread starvation, and avoiding common pitfalls in multithreading.
- Multiple examples are provided to understand how to use
Handling Thread Deadlocks and Interruptions:
Learn how improper usage of
join()
can result in deadlocks, where threads wait indefinitely for each other, and how to resolve such issues.Understand how the
sleep()
method interacts with thread interruptions and how to handleInterruptedException
.
By the end of this chapter, you will have a thorough understanding of how to manage thread priorities, coordinate threads with join()
, and control thread execution times with sleep()
. You’ll be equipped to handle various threading scenarios efficiently, avoiding issues like deadlocks and ensuring that your multithreaded applications run smoothly.
1.Priority of Threads in Java
In Java, multithreading is a crucial concept that enables multiple threads to execute concurrently. The thread priority mechanism helps determine the order in which threads are executed when CPU time is limited.
Understanding Thread Priority with Thread.currentThread().getPriority()
The method
Thread.currentThread().getPriority()
is used to retrieve the priority of the currently executing thread.Java provides two key methods to manage thread priorities:
setPriority(int priorityNumber)
: Assigns a new priority to the thread.getPriority()
: Retrieves the priority value of the thread.
Thread Scheduler and Execution Priority
The thread scheduler, a component of the JVM, plays a pivotal role in managing thread execution. Here's the process:
The OS provides CPU resources (e.g., 3 seconds) to the JVM.
The JVM delegates this time to the thread scheduler.
The thread scheduler decides which thread will execute based on their priority.
Decision Basis for Execution
Threads are executed based on their priority values.
If multiple threads have the same priority, the thread scheduler employs an algorithm to decide. This algorithm is vendor-dependent and is not disclosed by the vendor.
The priority levels range from 1 (MIN_PRIORITY) to 10 (MAX_PRIORITY), with the default being 5 (NORM_PRIORITY).
Traffic Circle Example (Priority Analogy)
To understand thread priorities, consider the example of managing traffic at a roundabout:
Dignitaries like a PM of another country or a CM are given higher priority.
A local MLA or common citizens get comparatively lower priority.
Similarly, in threads, higher priority threads are likely to execute first, provided resources are available.
Default Thread Priority
The default priority for the main thread is 5.
Child threads inherit their priority from the parent thread.
- For example, if the main thread has a priority of 5, a child thread created from the main thread will also have a priority of 5.
JVM Behavior:
The main thread is automatically created by the JVM and starts with a default priority of 5.
This inheritance ensures consistency in priority unless explicitly changed.
Example 1: Default Priority of Threads
class MyThread extends Thread {
public void run() {
System.out.println("Priority of Child thread is " + Thread.currentThread().getPriority());
}
}
public class Multithreading30 {
public static void main(String[] args) {
System.out.println("Priority of main thread is " + Thread.currentThread().getPriority());
MyThread t = new MyThread();
t.start();
}
}
Output:
Priority of main thread is 5
Priority of Child thread is 5
Explanation:
The main thread begins execution with a default priority of 5.
A child thread (
MyThread
) is created and inherits the priority of the main thread.When both threads execute, their priorities are displayed, confirming the inheritance mechanism.
Key Points on Thread Priority
Inheritance of Priority:
The priority of a child thread is inherited from its parent thread.
This ensures seamless execution without requiring explicit priority assignment unless necessary.
Role of Thread Scheduler:
Determines the execution order based on priority.
Uses vendor-specific algorithms when threads have the same priority.
Default Priority:
Main thread: 5.
Other threads: Inherit from parent threads unless explicitly set.
Dynamic Priority Adjustment:
- Using
setPriority(int priorityNumber)
, developers can dynamically change thread priorities as needed.
- Using
Visual Representation of Thread Scheduling:
OS
| 3 sec
JVM<-----CPU
|
Thread Scheduler
_______|________
| |
main thread Thread-0 (child thread)
Here, the Thread Scheduler determines which thread (main or Thread-0) will execute based on priority. Both threads having the same default priority (5
) leave the decision to the scheduler's internal algorithm.
2.Setting Priority of Threads in Java
Thread priority is a key feature of Java’s multithreading capabilities, allowing developers to influence the execution order of threads. Higher-priority threads are more likely to get CPU time, but execution is still subject to the thread scheduler's algorithm and the underlying operating system's support for prioritization.
How to Set Thread Priority?
Java provides the setPriority(int priorityNumber)
method to explicitly assign a priority to a thread.
Code Example: Assigning a Priority
MyThread t = new MyThread();
t.setPriority(10); // Setting the child thread's priority to MAX_PRIORITY (10)
By default, the main thread has a priority of 5 (NORM_PRIORITY).
After calling
t.start()
, there are two active threads:Main thread with priority 5.
Child thread (
MyThread
) with priority 10.
Execution Order
The thread with the higher priority (child thread with priority 10) gets CPU time first.
The thread scheduler allocates CPU time to higher-priority threads, making them execute sooner, provided the OS supports priority-based scheduling.
If the OS does not support scheduling or uses a different algorithm, the execution order may be unpredictable.
Thread Priority Ranges
Valid Priority Range:
Java supports priorities from 1 (lowest priority) to 10 (highest priority).
Assigning a value outside this range results in an IllegalArgumentException.
Predefined Constants:
Thread.MIN_PRIORITY
= 1Thread.NORM_PRIORITY
= 5Thread.MAX_PRIORITY
= 10
Priority Constants in java.lang.Thread
You can inspect these constants by running:
javap java.lang.Thread
The output will display:
public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5; // Normal priority
public static final int MAX_PRIORITY = 10;
Important Notes:
Java does not have predefined constants like
Thread.LOW_PRIORITY
orThread.HIGH_PRIORITY
. Instead, use the predefined minimum, normal, and maximum constants.Threads with equal priority depend on the thread scheduler's vendor-dependent algorithm, making execution order unpredictable in such cases.
Visual Representation of Thread Priority Allocation
Thread Scheduler
_______|________
| |
thread priority- 5 10
- The scheduler allocates CPU time to the thread with priority 10 first.
Example 2: Setting Thread Priority
class MyThread extends Thread {
public void run() {
System.out.println("Priority of Child thread is " + Thread.currentThread().getPriority());
}
}
public class Multithreading31 {
public static void main(String[] args) {
System.out.println("Priority of main thread is " + Thread.currentThread().getPriority());
MyThread t = new MyThread();
t.setPriority(10); // Setting the child thread's priority to 10
t.start(); // Starting the child thread
}
}
Output:
Priority of main thread is 5
Priority of Child thread is 10
Execution Flow:
The main thread executes first and displays its priority as 5.
The child thread is created and assigned a priority of 10.
The thread scheduler allocates CPU time to the child thread first, as it has a higher priority than the main thread.
Key Observations:
Impact of High Priority:
The child thread (priority 10) executes before the main thread (priority 5) because the scheduler prioritizes higher-priority threads.
However, this behavior relies on the OS supporting priority-based scheduling.
Execution on Cracked or Unsupported OS:
- On systems with improper thread scheduling support (e.g., cracked versions of Windows), the execution order might not align with the assigned priorities, leading to unexpected results.
Summary of Thread Priority Management
Default Priority:
Main thread: 5 (NORM_PRIORITY).
Child threads: Inherit the priority of the parent unless explicitly changed.
Range and Constants:
Priority range: 1 to 10.
Constants:
MIN_PRIORITY
= 1,NORM_PRIORITY
= 5,MAX_PRIORITY
= 10.
Setting and Getting Priority:
- Use
setPriority(int)
to set andgetPriority()
to retrieve thread priorities.
- Use
Scheduling Dependencies:
- Priority influences execution order but depends on the OS and JVM.
Example 3: Invalid Priority Value
Allowed Priority Range in Java:
In Java, the valid range for thread priority is 1 to 10.
Setting a priority outside this range results in a runtime exception (
IllegalArgumentException
).
Code Walkthrough:
class MyThread extends Thread { public void run() { System.out.println("Priority of Child thread is " + Thread.currentThread().getPriority()); } } public class Multithreading32 { public static void main(String[] args) { System.out.println("Priority of main thread is " + Thread.currentThread().getPriority()); MyThread t = new MyThread(); t.setPriority(100); // Invalid priority value t.start(); } }
Detailed Explanation of Steps:
Step 1: The program begins in the
main
thread.- The priority of the
main
thread is fetched usingThread.currentThread().getPriority()
and is displayed. By default, the priority is5
.
- The priority of the
Step 2: A new
MyThread
objectt
is created, which extendsThread
. At this point, therun
method ofMyThread
is not yet executed.Step 3: The
setPriority(100)
method is called to set the priority of the threadt
.Since
100
is outside the valid range (1-10), Java throws anIllegalArgumentException
at runtime.The JVM stops execution at this point, and the
t.start()
line is never reached.
Output Analysis:
The priority of the main thread (
5
) is printed.The program terminates with an exception:
Exception in thread "main" java.lang.IllegalArgumentException at java.base/java.lang.Thread.setPriority(Thread.java:1138) at Multithreading32.main(Multithreading32.java:185)
Key Learnings:
The valid range for
Thread.setPriority()
is 1 (MIN_PRIORITY) to 10 (MAX_PRIORITY).Any value outside this range causes a runtime exception.
Although
setPriority()
accepts integers as parameters, validation is performed during runtime.
Example 4: Setting Priority After Starting the Thread
What Happens When We Set Priority After
start()
?:Java allows setting a thread’s priority using
setPriority(int priorityNumber)
before starting the thread usingstart()
.If
setPriority()
is called after the thread starts, the new priority does not affect the already executing thread, but Java does not block such a call.
Code Walkthrough:
class MyThread extends Thread { public void run() { System.out.println("Priority of Child thread is " + Thread.currentThread().getPriority()); for (int i = 0; i < 5; i++) { System.out.println("Child thread"); } } } public class Multithreading32 { public static void main(String[] args) { MyThread t = new MyThread(); t.start(); // Thread execution begins t.setPriority(100); // Invalid value after start System.out.println("Priority of main thread is " + Thread.currentThread().getPriority()); for (int i = 0; i < 5; i++) { System.out.println("Main thread"); } } }
Detailed Explanation of Steps:
Step 1: The
main
thread starts.- A
MyThread
objectt
is created, but itsrun()
method is not yet invoked.
- A
Step 2: The
t.start()
method is called, signaling the JVM to schedule therun()
method ofMyThread
on a new thread.- At this point, the child thread (
t
) begins execution with its current priority, which defaults to5
.
- At this point, the child thread (
Step 3: The
setPriority(100)
method is called aftert.start()
.- Since
100
is outside the valid range, Java throws anIllegalArgumentException
. However, the child thread continues executing.
- Since
Step 4: The
main
thread continues execution in parallel, printing its default priority and the "Main thread" message.
Output Analysis:
The child thread’s priority is printed as
5
, as the invalid priority change (100
) does not succeed.Both threads execute their respective loops, but the exception interrupts the normal flow:
Priority of Child thread is 5 Child thread Child thread Child thread Child thread Child thread Exception in thread "main" java.lang.IllegalArgumentException at java.base/java.lang.Thread.setPriority(Thread.java:1138) at Multithreading32.main(Multithreading32.java:63)
Key Learnings:
Calling
setPriority(int priorityNumber)
after a thread has started does not alter its scheduling immediately.Any invalid value for priority results in a runtime exception, regardless of when
setPriority()
is called.Proper thread management requires setting priorities before starting the thread.
Preventing Threads from Execution Using yield()
, sleep()
, and join()
4. Methods to Prevent (Stop) Thread Execution
In multithreading, there are scenarios where threads need to pause or stop their execution temporarily. Java provides three primary methods to achieve this:
yield()
sleep()
join()
Thread Lifecycle Overview
Before diving into the methods, let's understand the lifecycle of a thread:
New/Born State:
- The thread is created using
Thread t = new Thread();
but not yet started.
- The thread is created using
Ready/Runnable State:
- When
t.start()
is called, the thread enters the Runnable state, waiting for the CPU to allocate execution time.
- When
Running State:
- Once the CPU allocates time, the thread enters the Running state.
Dead State:
- After completing the
run()
method, the thread enters the Dead state.
- After completing the
Thread Lifecycle Diagram:
New/Born -----------> Ready/Runnable -----------> Running -----------> Dead
| |
main thread child thread
1. yield()
Method
Purpose:
The
yield()
method pauses the currently executing thread to give a chance to other threads of the same priority to execute.It is a static native method in Java (
public static native void yield()
).If there are no threads of the same or higher priority, the current thread continues execution.
How it Works:
A thread in the Running state calls
yield()
:- The thread transitions back to the Ready/Runnable state.
The Thread Scheduler decides when to allocate CPU time back to the yielded thread.
Behavior:
If there are waiting threads of the same priority, they might execute.
If all threads have the same priority, the thread to execute next is chosen randomly by the Thread Scheduler.
Note: The behavior of
yield()
is platform-dependent and may not guarantee fairness.
Code Example: yield()
Usage
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
Thread.yield(); // Line-1
System.out.println("Child thread");
}
}
}
public class Multithreading33 {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // Child thread enters Runnable state
for (int i = 0; i < 5; i++) {
System.out.println("Main thread");
}
}
}
Output Analysis:
First Execution:
Main thread Main thread Main thread Main thread Main thread Child thread Child thread Child thread Child thread Child thread
Second Execution:
Main thread Main thread Child thread Child thread Main thread Main thread Main thread Child thread Child thread
Explanation:
In the first run, the
Main thread
completed its execution before theChild thread
.In the second run, both threads alternated execution due to context switching, controlled by the
Thread Scheduler
.If
Line-1
(yield) is commented, the thread with higher priority or randomly chosen thread will dominate.
Key Points:
yield()
is platform-dependent, and its behavior may vary.Threads with equal priority might still not execute in a predictable order.
It does not guarantee immediate suspension; it depends entirely on the Thread Scheduler.
Real-Life Analogy:
Imagine a telephone booth with multiple people (threads) in line.
Person-A (thread) is talking on the phone.
Person-A uses
yield()
to pause and let Person-B (another thread) talk if they are of equal priority.If Person-C is also waiting, the owner (Thread Scheduler) decides who gets the next turn.
Thread State Transition for yield()
:
Running ----> Ready/Runnable ----> Running (when CPU time is allocated)
2. join()
Method
Overview
The join()
method allows one thread to wait for the completion of another thread. In simpler terms, if thread t1
calls t2.join()
, t1
will pause its execution until thread t2
finishes.
Prototype of join()
The join()
method comes in three variations:
public final void join() throws InterruptedException
- Waits indefinitely until the target thread finishes execution.
public final void join(long ms) throws InterruptedException
- Waits for a specified time in milliseconds for the target thread to complete.
public final void join(long ms, int ns) throws InterruptedException
- Waits for the specified time in milliseconds and nanoseconds for the target thread to complete.
Explanation Through a Scenario
Scenario-1: Thread Waiting Until Another Thread Completes
Two teachers, Hyder Sir (t1) and Nithin Sir (t2), are conducting sessions:
Hyder Sir (t1) finishes at 10:45 AM.
Nithin Sir (t2) extends his session until 12:00 PM.
Two friends (threads) need to return to their PG together.
Friend A (t1) waits for Friend B (t2) until Nithin Sir finishes.
Once t2 completes, t1 joins it, and they proceed together.
Scenario-2: Thread Interrupted During Waiting
If another person (thread) interrupts Friend A (t1) during their wait, it throws an InterruptedException
. This represents an external disruption.
Key Points
Waiting State:
- When
t1.join()
is called,t1
enters the waiting state untilt2
completes.
- When
InterruptedException:
If another thread interrupts
t1
while it is waiting fort2
, anInterruptedException
is thrown.This is a checked exception that must be handled.
Thread Waiting Scenarios
Thread waits until the other thread completes execution:
t1.join()
waits indefinitely fort2
.
Thread waits for a specified duration:
t1.join(30000)
makest1
wait for 30 seconds.After the specified duration,
t1
continues, whether or nott2
has completed.
Thread waits for a specific time in milliseconds and nanoseconds:
t1.join(30000, 500)
makest1
wait for 30 seconds and 500 nanoseconds.
Code Example: Using join()
class MyThread extends Thread {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("Child thread: " + i);
try {
Thread.sleep(1000); // Simulate some work
} catch (InterruptedException e) {
System.out.println("Child thread interrupted");
}
}
}
}
public class MultithreadingJoinExample {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // Start child thread
try {
System.out.println("Main thread waiting for child thread to complete...");
t.join(); // Main thread waits for child thread
} catch (InterruptedException e) {
System.out.println("Main thread interrupted");
}
System.out.println("Main thread resumes after child thread completion.");
}
}
Output
Main thread waiting for child thread to complete...
Child thread: 1
Child thread: 2
Child thread: 3
Child thread: 4
Child thread: 5
Main thread resumes after child thread completion.
Explanation
The main thread starts first and then waits for the child thread to finish execution using
t.join()
.The child thread executes its task and completes.
After the child thread finishes, the main thread resumes.
Interruption Scenario
If another thread interrupts the waiting thread, an InterruptedException
occurs. This is demonstrated below:
Code Example: InterruptedException
public class MultithreadingInterruptExample {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("Child thread: " + i);
try {
Thread.sleep(1000); // Simulate some work
} catch (InterruptedException e) {
System.out.println("Child thread interrupted");
}
}
});
t.start();
try {
System.out.println("Main thread waiting for child thread...");
t.join();
} catch (InterruptedException e) {
System.out.println("Main thread interrupted");
}
// Simulating interruption
if (t.isAlive()) {
t.interrupt(); // Interrupts child thread
}
}
}
Output
Main thread waiting for child thread...
Child thread: 1
Child thread: 2
Child thread interrupted
Main thread interrupted
Summary
join()
ensures thread synchronization by allowing one thread to wait for another.It can handle specific durations and interruptions using overloaded methods.
InterruptedException
must always be handled to avoid runtime errors.
Scenario-3: Marriage Planning with Dependent Actions
This scenario models dependent tasks (Venue Fixing, Wedding Card Printing, Wedding Card Distribution). Each task depends on the completion of the previous one. This represents thread dependencies and join states, which are critical in thread-based multithreading.
Key Details:
Thread Execution States:
t1
must complete beforet2
starts (t1.join()
).t2
must complete beforet3
starts (t2.join()
).Each thread has a dead state after completion.
InterruptedException:
If a thread is interrupted while waiting, it throws an
InterruptedException
.This must be handled using a
try-catch
block.
Thread Scheduling:
Main thread waits for each task to complete using
join()
.Execution is sequential, preserving dependency.
Conceptual Flow
Venue Fixing -> Wedding Card Printing -> Wedding Card Distribution
t1 t2 t3
|---------------|----------------------|
t1.join() t2.join()
Updated Code with Commented Sections
// Thread for Venue Fixing (Task 1)
class VenueFixing extends Thread {
@Override
public void run() {
System.out.println("Venue Fixing: Task started...");
try {
Thread.sleep(2000); // Simulate time taken for venue fixing
} catch (InterruptedException e) {
System.out.println("Venue Fixing was interrupted!");
}
System.out.println("Venue Fixing: Task completed.");
}
}
// Thread for Wedding Card Printing (Task 2)
class WeddingCardPrinting extends Thread {
@Override
public void run() {
System.out.println("Wedding Card Printing: Task started...");
try {
Thread.sleep(3000); // Simulate time taken for card printing
} catch (InterruptedException e) {
System.out.println("Wedding Card Printing was interrupted!");
}
System.out.println("Wedding Card Printing: Task completed.");
}
}
// Thread for Wedding Card Distribution (Task 3)
class WeddingCardDistribution extends Thread {
@Override
public void run() {
System.out.println("Wedding Card Distribution: Task started...");
try {
Thread.sleep(1000); // Simulate time taken for distribution
} catch (InterruptedException e) {
System.out.println("Wedding Card Distribution was interrupted!");
}
System.out.println("Wedding Card Distribution: Task completed.");
}
}
// Main class demonstrating thread dependencies
public class MarriagePlanning {
public static void main(String[] args) throws InterruptedException {
// Step 1: Initialize threads
VenueFixing t1 = new VenueFixing();
WeddingCardPrinting t2 = new WeddingCardPrinting();
WeddingCardDistribution t3 = new WeddingCardDistribution();
// Step 2: Start and synchronize threads using join()
System.out.println("Wedding Preparation Begins...");
t1.start(); // Start Venue Fixing
t1.join(); // Main thread waits for t1 to complete
System.out.println("Venue Fixing is complete. Proceeding to card printing...");
t2.start(); // Start Wedding Card Printing
t2.join(); // Main thread waits for t2 to complete
System.out.println("Card Printing is complete. Proceeding to card distribution...");
t3.start(); // Start Wedding Card Distribution
t3.join(); // Main thread waits for t3 to complete
System.out.println("All tasks completed. Wedding preparation is done!");
}
}
Output
Wedding Preparation Begins...
Venue Fixing: Task started...
Venue Fixing: Task completed.
Venue Fixing is complete. Proceeding to card printing...
Wedding Card Printing: Task started...
Wedding Card Printing: Task completed.
Card Printing is complete. Proceeding to card distribution...
Wedding Card Distribution: Task started...
Wedding Card Distribution: Task completed.
All tasks completed. Wedding preparation is done!
Critical Notes Addressed
Task Dependencies:
The use oft1.join()
andt2.join()
ensures that the tasks execute in a specific sequence.Dead States:
Each thread transitions to a dead state after completing its task and the main thread resumes its operation.InterruptedException Handling:
EachThread.sleep()
call is wrapped in atry-catch
block to handle interruptions gracefully.Sequential Flow with Messages:
Console output clearly indicates the progress of each task and its dependency.Enhanced Realism:
By simulating delays usingThread.sleep()
, the scenario mirrors real-world multithreading behavior.
Visualization
Thread States
t1.start() -> t1 (Running) -> t1.join() -> t1 (Dead)
t2.start() -> t2 (Running) -> t2.join() -> t2 (Dead)
t3.start() -> t3 (Running) -> t3.join() -> t3 (Dead)
Key Learnings
Sequential Execution with
join()
:- Ensures task dependencies are respected.
Checked Exception Handling:
- The
join()
method throwsInterruptedException
, a checked exception that must be handled in code.
- The
Dead State:
- After a thread completes its task, it transitions to the dead state.
Case-2 Explanation: Using public final void join(long ms)
Method
In Case-2, we will use the join(long ms)
method where the parent thread (main thread) will wait for the child thread to finish for a specified time (in this case, 1000 milliseconds).
The key observation here is that the parent thread does not completely wait for the child thread to finish. Instead, it will wait for the specified time (1 second) before resuming its execution.
Concept Explanation
join(long ms)
Method:The
join(long ms)
method causes the parent thread to wait for a maximum ofms
milliseconds for the child thread to finish.If the child thread completes before
ms
milliseconds, the parent thread will resume execution immediately.If the child thread takes longer than the specified time, the parent thread will continue after the specified timeout.
Parent and Child Thread Execution:
The parent thread starts, then the child thread begins execution.
The parent thread calls
t.join(1000)
, which makes it wait for 1 second (1000 milliseconds).After the 1-second wait, the parent thread continues its execution, and the child thread may still be running (if it hasn’t completed within 1 second).
Code with Explanation and Numbering
// Step-1: Defining the Child Thread
class MyThread extends Thread {
@Override
public void run() {
// Step-2: Child thread prints "Child thread" 5 times with a 2-second delay between each print
for (int i = 0; i < 5; i++) {
System.out.println("Child thread");
try {
// Step-3: Pausing the execution for 2 seconds (2000 milliseconds)
Thread.sleep(2000);
} catch (InterruptedException e) {
// Step-4: Handle InterruptedException if thrown
System.out.println("Child thread interrupted!");
}
}
}
}
// Step-5: Main Class with Main Method
public class MultithreadingJoinExample {
public static void main(String[] args) throws InterruptedException {
// Step-6: Create and start the child thread
MyThread t = new MyThread();
t.start();
// Step-7: Main thread calls join(1000), causing it to wait for 1 second before continuing
t.join(1000); // Line-1: Parent thread waits for 1 second
// Step-8: Main thread continues executing after 1 second
for (int i = 0; i < 5; i++) {
System.out.println("Parent thread");
}
}
}
Explanation of Execution
Step-6 (Child Thread Creation and Start):
A new thread
t
is created from theMyThread
class and started usingt.start()
. This triggers therun()
method in theMyThread
class.Inside the
run()
method, the child thread will print "Child thread" 5 times, with a 2-second pause between each print.
Step-7 (Parent Thread Waits for 1 Second):
After starting the child thread, the main (parent) thread calls
t.join(1000)
. This tells the main thread to wait for the child thread to complete for 1000 milliseconds (1 second).The main thread will not completely block, but it will pause for 1 second while the child thread is still running.
Step-8 (Parent Thread Continues Execution):
After 1 second, the parent thread resumes execution and prints "Parent thread" 5 times, irrespective of whether the child thread has completed its execution or not.
The child thread, if it hasn’t finished its loop, will continue executing independently of the main thread.
Output:
Here’s the output when you run this code:
Child thread
Parent thread
Parent thread
Parent thread
Parent thread
Parent thread
Child thread
Child thread
Child thread
Child thread
Child thread
Key Observations
Parent Thread Executes Before Child:
- The parent thread prints "Parent thread" after 1 second, even though the child thread might not have finished its execution.
Concurrency:
- After 1 second, the parent thread continues execution independently from the child thread.
Timing Difference:
- Notice that the parent thread starts printing "Parent thread" immediately after waiting for 1 second, while the child thread prints "Child thread" based on its own sleep cycle (which is 2 seconds).
Thread Scheduling:
- Since thread scheduling is dependent on the operating system and JVM, you may see slight variations in the output order depending on how the threads are scheduled.
Behavior Comparison
Method | Behavior |
t.join() | The parent thread waits until the child thread completes all iterations. |
t.join(1000) | The parent thread waits for 1 second and then resumes independently. |
No join (t.join() ) | The output is unpredictable due to thread scheduler behavior. |
Key Notes
Predictability:
With
t.join(1000)
, the parent thread executes after a predictable 1-second delay.Without any
join
, the behavior depends on the thread scheduler.
Concurrency:
- This demonstrates a non-blocking join where the parent thread and child thread execute concurrently after the timeout.
InterruptedException:
- The
InterruptedException
must be handled as it’s a checked exception.
- The
Thread Scheduling:
- Thread scheduling is determined by the operating system, which may cause slight variations in execution order.
Unpredictable Behavior: If we change the
join
timeout or omit it, the thread scheduling may behave unpredictably, but the general pattern will remain consistent.InterruptedException
Handling: Always handleInterruptedException
when usingThread.sleep()
orjoin()
methods.
Conclusion
The
join(long ms)
method causes the parent thread to wait for up toms
milliseconds for the child thread to finish.In this case, the parent thread waits for 1 second (
join(1000)
), then continues executing independently, while the child thread may still be running.If
t.join()
(without a timeout) were used, the parent thread would wait for the child thread to complete completely before continuing execution.
Case-3 Explanation: Using public final void join(long ms, int ns)
Method
In Case-3, we are using the join(long ms, int ns)
method, where the parent thread is waiting for the child thread to finish its execution for a more precise amount of time: milliseconds and nanoseconds.
This method gives the parent thread more control over the time it waits for the child thread. It allows for specifying both milliseconds and nanoseconds, which can provide finer-grained control over waiting times.
Concept Explanation
join(long ms, int ns)
Method:The
join(long ms, int ns)
method causes the current thread (parent thread) to wait for at mostms
milliseconds andns
nanoseconds for the child thread to finish its execution.This method is available in the
java.lang.Thread
class and is useful when you want to wait for a combination of milliseconds and nanoseconds before the parent thread proceeds.
Parent and Child Thread Execution:
The parent thread starts, then the child thread begins execution.
The parent thread calls
t.join(100, 10)
, which means it will wait for 100 milliseconds and 10 nanoseconds for the child thread to complete before it resumes execution.After the specified time, the parent thread will continue, whether or not the child thread has finished its execution.
Code with Explanation and Numbering
// Step-1: Defining the Child Thread
class MyThread extends Thread {
@Override
public void run() {
// Step-2: Child thread prints "Child thread" 5 times with a 2-second delay between each print
for (int i = 0; i < 5; i++) {
System.out.println("Child thread");
try {
// Step-3: Pausing the execution for 2 seconds (2000 milliseconds)
Thread.sleep(2000);
} catch (InterruptedException e) {
// Step-4: Handle InterruptedException if thrown
System.out.println("Child thread interrupted!");
}
}
}
}
// Step-5: Main Class with Main Method
public class MultithreadingJoinExample {
public static void main(String[] args) throws InterruptedException {
// Step-6: Create and start the child thread
MyThread t = new MyThread();
t.start();
// Step-7: Main thread calls join(100, 10), causing it to wait for 100 milliseconds and 10 nanoseconds before continuing
t.join(100, 10); // Line-1: Parent thread waits for 100 milliseconds and 10 nanoseconds
// Step-8: Main thread continues executing after waiting for the child thread
for (int i = 0; i < 5; i++) {
System.out.println("Parent thread");
}
}
}
Explanation of Execution
Step-6 (Child Thread Creation and Start):
A new thread
t
is created from theMyThread
class and started usingt.start()
. This triggers therun()
method in theMyThread
class.Inside the
run()
method, the child thread will print "Child thread" 5 times, with a 2-second pause between each print.
Step-7 (Parent Thread Waits for 100 ms and 10 ns):
After starting the child thread, the main (parent) thread calls
t.join(100, 10)
. This tells the main thread to wait for 100 milliseconds and 10 nanoseconds for the child thread to complete.The parent thread will wait for this exact time, after which it will resume its execution, regardless of whether the child thread is finished or not.
Step-8 (Parent Thread Continues Execution):
After the 100 milliseconds and 10 nanoseconds, the parent thread resumes and prints "Parent thread" 5 times.
Meanwhile, the child thread may still be running, and its output will be printed afterward.
Output:
Here’s the output when you run this code:
Child thread
Parent thread
Parent thread
Parent thread
Parent thread
Parent thread
Child thread
Child thread
Child thread
Child thread
Key Observations
Parent Thread Executes After Waiting for the Specified Time:
The parent thread will start printing "Parent thread" after waiting for exactly 100 milliseconds and 10 nanoseconds.
While the parent thread waits, the child thread continues its execution, printing "Child thread" 5 times.
Child Thread's Longer Execution:
The child thread prints "Child thread" 5 times, but it executes for longer than the parent thread’s wait time (because of the
Thread.sleep(2000)
).This means that the parent thread may finish printing "Parent thread" before the child thread completes its loop.
Precise Waiting Time:
- The use of milliseconds and nanoseconds in
join(long ms, int ns)
provides more precise control over the waiting time compared to just using milliseconds.
- The use of milliseconds and nanoseconds in
Thread Scheduling:
- The exact sequence of printed statements can depend on thread scheduling by the JVM and operating system, so you might see slight variations in the exact order of "Child thread" and "Parent thread" outputs.
Conclusion
The
join(long ms, int ns)
method gives the parent thread more precise control over the wait time by allowing it to specify both milliseconds and nanoseconds.After waiting for the specified time, the parent thread continues executing, while the child thread may still be running or may have completed its execution.
This method is useful when you need to wait for a very specific time before the parent thread proceeds, such as when working with tasks that require fine-grained synchronization.
Key Notes:
Unpredictable Behavior: As always, thread scheduling is dependent on the JVM and OS, so the exact order of outputs can vary slightly across different executions.
Interrupt Handling: Make sure to handle
InterruptedException
when usingThread.sleep()
orjoin()
methods.
Case-4 Explanation: Child Thread Waiting for Parent Thread
In Case-4, we explore a scenario where the child thread waits for the parent (main) thread to complete its execution. This is achieved using the join()
method in the child thread, where the child thread waits for the parent thread to finish before it starts or continues its execution.
Here’s a step-by-step explanation:
Concept Explanation
Child Thread Waiting for Parent Thread:
In this case, the child thread is not executed immediately; instead, it waits for the parent (main) thread to complete.
The parent thread starts first and the child thread waits for the parent to finish by calling
join()
on the parent thread’s reference.
Using the
join()
Method in the Child Thread:The
join()
method is called from the child thread’srun()
method to ensure that the child thread waits for the parent thread to complete before proceeding.This means that the child thread will only execute after the parent thread has finished its task, ensuring the parent thread executes first.
Thread Synchronization:
- This approach is a form of thread synchronization where one thread (the child) waits for another thread (the parent) to finish before proceeding.
Code Explanation with Numbering
// Step-1: Defining the Child Thread Class
class MyThread extends Thread {
static Thread mt; // Step-2: Static reference to the Parent thread
@Override
public void run() {
try {
// Step-3: Child thread waits for the Parent thread to finish
mt.join(); // Child thread waits for Parent thread
} catch (InterruptedException e) {
// Step-4: Handle InterruptedException if it occurs
e.printStackTrace();
}
// Step-5: Child thread prints "child thread" 5 times after waiting for Parent thread
for (int i = 1; i <= 5; i++) {
System.out.println("child thread");
}
}
}
// Step-6: Main Class to Run the Threads
public class Multithreading38 {
public static void main(String[] args) throws InterruptedException {
// Step-7: The reference of the Parent thread is set
MyThread.mt = Thread.currentThread(); // Parent thread (main thread) reference
// Step-8: Create and start the Child thread
MyThread t = new MyThread();
t.start(); // Child thread starts
// Step-9: Parent thread sleeps for 2 seconds, allowing the Child thread to execute after waiting
for (int i = 0; i < 5; i++) {
Thread.sleep(2000); // Parent thread sleeps for 2 seconds
System.out.println("Parent thread"); // Parent thread prints
}
}
}
Explanation of Execution Flow
Step-7 (Setting Parent Thread Reference):
- In the
main
method, the reference of the parent thread (main
thread) is set to the static fieldmt
of theMyThread
class. This allows the child thread to access the parent thread reference later.
- In the
Step-8 (Creating and Starting the Child Thread):
- A new
MyThread
object (t
) is created, and thestart()
method is called to begin the execution of the child thread.
- A new
Step-9 (Parent Thread Sleeps):
- The parent thread (main thread) enters a loop and prints "Parent thread" five times, with a 2-second delay between each print (
Thread.sleep(2000)
).
- The parent thread (main thread) enters a loop and prints "Parent thread" five times, with a 2-second delay between each print (
Step-3 (Child Thread Waits for Parent Thread):
- Inside the child thread's
run()
method, themt.join()
method is called. Sincemt
refers to the parent thread (the main thread), the child thread waits for the parent thread to finish before it proceeds.
- Inside the child thread's
Step-5 (Child Thread Prints):
- After the parent thread finishes its loop and printing, the child thread resumes and prints "child thread" five times.
Output
Here is the expected output when running this code:
Parent thread
Parent thread
Parent thread
Parent thread
Parent thread
child thread
child thread
child thread
child thread
child thread
Key Observations
Parent Thread Executes First:
- The parent thread prints "Parent thread" five times, with a 2-second interval between each print. This happens before the child thread starts executing.
Child Thread Waits for Parent Thread:
- The child thread waits for the parent thread to finish before it starts printing "child thread". The call to
mt.join()
ensures that the parent thread completes its task first.
- The child thread waits for the parent thread to finish before it starts printing "child thread". The call to
Synchronization:
- This case demonstrates thread synchronization, where the child thread waits for the parent thread to finish using the
join()
method. This is useful when the execution order of threads is important.
- This case demonstrates thread synchronization, where the child thread waits for the parent thread to finish using the
Conclusion
In Case-4, we’ve created a situation where the child thread waits for the parent thread to finish using the join()
method. The child thread does not proceed until the parent thread completes its execution. This ensures a specific execution order, where the parent thread executes first and the child thread executes afterward.
Important Notes:
Thread Synchronization: This approach guarantees that the parent thread completes its task before the child thread starts, which can be crucial in certain scenarios where the child depends on the completion of the parent.
Handling
InterruptedException
: Always handleInterruptedException
when working with thread-related methods likejoin()
andsleep()
to prevent unexpected behavior when the thread is interrupted.
Case-5: Deadlock Caused by join()
Method
In Case-5, we explore a deadlock scenario caused by the improper use of the join()
method. Deadlock occurs when two threads are each waiting for the other to finish, causing both threads to be stuck in an infinite wait state.
Concept Explanation
Deadlock in Multithreading:
A deadlock happens when a thread waits indefinitely for a condition that cannot be met because it’s also waiting on that thread. In this case, if a thread waits for itself to finish, it will never proceed, leading to a deadlock.
Deadlock occurs in our example when a thread calls
join()
on itself. This results in the thread waiting for itself to finish, which is impossible, and causes an infinite wait state.
Join on the Same Thread:
- When a thread calls
join()
on itself, likeThread.currentThread().join()
, the thread waits for itself to complete, which creates a situation where the thread is stuck indefinitely, causing infinite waiting.
- When a thread calls
Code Explanation with Numbering
// Example-1: Deadlock due to Thread joining with itself
// Step-1: Child thread class extends Thread
class MyThread extends Thread {
static Thread mt; // Static reference to parent (main) thread
@Override
public void run() {
try {
// Step-2: Child thread waits for parent thread to finish using join
mt.join(); // Deadlock happens here: Parent thread joins with itself
} catch (InterruptedException e) {
e.printStackTrace();
}
// Step-3: Print "child thread" 5 times after waiting for parent thread
for (int i = 1; i <= 5; i++) {
System.out.println("child thread");
}
}
}
// Step-4: Main class
public class Multithreading39 {
public static void main(String[] args) throws InterruptedException {
// Step-5: Main thread attempts to join with itself, causing a deadlock
Thread.currentThread().join(); // Main thread joins with itself
}
}
Explanation of Execution Flow
Step-5 (Main Thread Joins with Itself):
- The main thread calls
Thread.currentThread().join()
, which means the main thread is waiting for itself to finish. This is an infinite wait because a thread cannot complete while waiting for itself.
- The main thread calls
Deadlock Situation:
- Since the main thread is stuck waiting for itself, it will never finish, and the program will not produce any output. This is a deadlock, where the main thread is waiting indefinitely, causing the program to freeze.
No Output:
- The program does not proceed to any further execution (e.g., printing anything) because the main thread is stuck in the
join()
method, waiting for itself, which never happens.
- The program does not proceed to any further execution (e.g., printing anything) because the main thread is stuck in the
Output for Example-1
(no output, program hangs indefinitely)
Example-2: Deadlock Between Parent and Child Threads
In this example, we explore a case where a parent (main) thread and child thread are both trying to wait for each other to finish. This results in a deadlock because both threads are stuck waiting for the other to finish, and neither can proceed.
Code Explanation for Example-2
// Example-2: Deadlock between main thread and child thread
// Step-1: Child thread class extends Thread
class MyThread extends Thread {
static Thread mt; // Static reference to parent (main) thread
@Override
public void run() {
try {
// Step-2: Child thread waits for the main thread to finish using join
mt.join(); // Deadlock happens here: Child thread joins with main thread
} catch (InterruptedException e) {
e.printStackTrace();
}
// Step-3: Child thread prints "child thread" 10 times
for (int i = 1; i <= 10; i++) {
System.out.println("child thread");
}
}
}
// Step-4: Main class
public class Multithreading39 {
public static void main(String... args) throws InterruptedException {
// Step-5: Parent (main) thread sets its reference
MyThread.mt = Thread.currentThread(); // Main thread reference
// Step-6: Create and start the child thread
MyThread t = new MyThread();
t.start();
// Step-7: Main thread waits for the child thread to finish using join
t.join(); // Main thread waits for child thread
// Step-8: Main thread prints "main thread" 10 times
for (int i = 1; i <= 10; i++) {
System.out.println("main thread");
Thread.sleep(2000); // Main thread sleeps for 2 seconds
}
}
}
Explanation of Execution Flow for Example-2
Step-5 (Parent (Main) Thread Sets Its Reference):
- The main thread sets its reference in
MyThread.mt
, which allows the child thread to access it.
- The main thread sets its reference in
Step-6 (Creating and Starting the Child Thread):
- A child thread is created and started using
t.start()
.
- A child thread is created and started using
Step-7 (Main Thread Joins with the Child Thread):
- The main thread calls
t.join()
, which causes it to wait for the child thread to finish.
- The main thread calls
Step-2 (Child Thread Joins with the Main Thread):
- Inside the child thread’s
run()
method, the child callsmt.join()
, which is the main thread, causing the child to wait for the main thread to finish.
- Inside the child thread’s
Deadlock Situation:
- The main thread waits for the child thread to finish, but the child thread is waiting for the main thread to finish. As a result, both threads are stuck in an infinite wait, causing a deadlock.
No Output:
- Since both threads are stuck waiting for each other, the program does not proceed to print anything, resulting in a blank output.
Output for Example-2
(blank output, program hangs indefinitely)
Key Observations
Infinite Wait (Deadlock):
- In both examples, the program results in deadlock because a thread waits for itself (Example 1) or both threads wait for each other (Example 2), leading to infinite waiting.
Avoiding Deadlock:
- When using
join()
, make sure that a thread is not joining itself and that threads do not create circular dependencies by waiting for each other. In multithreading, always ensure that the waiting time is well-defined and avoid situations where threads can end up in infinite loops.
- When using
No Output Due to Deadlock:
- Both examples demonstrate that when deadlock occurs, no output is produced because the threads are stuck in the waiting state.
Conclusion
Deadlock is a common issue when working with multithreading. It can occur when threads wait indefinitely for each other to complete. In both examples above, we saw how joining a thread with itself or having two threads wait for each other results in an infinite wait, leading to deadlock. Always ensure proper thread synchronization to avoid such situations in multithreading applications.
Life of a Thread When join()
Method is Called
When the join()
method is called on a thread, it changes the state of the calling thread, typically causing it to enter a waiting state until the thread that it called join()
on completes its execution. The thread can move between different states based on certain conditions like completion, timeout, or interruption. Below, we'll break down how the thread moves through different states and what possibilities exist when using the join()
method.
Thread Lifecycle with join()
Thread States:
Ready/Runnable State: The thread is ready to run or is actively running.
Waiting State: The thread is not running but is waiting for some condition (like the completion of another thread) to become runnable again.
Running State: The thread is currently executing instructions.
When a thread calls the join()
method, it enters the waiting state and will remain there until the thread it is waiting on either finishes execution or the wait time expires (if a time-bound join()
is used).
Possibilities of Thread Entering Waiting State with join()
t.join()
:This makes the calling thread wait until the thread
t
has finished executing.The thread will stay in the waiting state until thread
t
completes its run.
t.join(100)
:This causes the calling thread to wait for a maximum of 100 milliseconds.
If thread
t
finishes execution before the timeout, the calling thread will move to the runnable state. If the timeout of 100 milliseconds elapses, the calling thread will also move to the runnable state.
t.join(100, 10)
:This specifies a timeout of 100 milliseconds and 10 nanoseconds.
After the timeout (100ms and 10ns), the calling thread will return to the runnable state, whether or not the thread
t
has completed.
Thread Movement Between States
When calling the join()
method, the thread goes through a few state transitions:
Ready/Runnable State → Waiting State (after calling
join()
)Initially, the calling thread is in the ready/runnable state.
After calling
join()
, it enters the waiting state until the thread it is waiting on finishes its execution.
Waiting State → Ready/Runnable State
The calling thread will return to the runnable state in the following cases:
Thread
t
finishes execution: The calling thread will be notified that threadt
has completed, and it can now resume.Timeout expires: If the timeout passed to
join()
(in milliseconds or nanoseconds) elapses, the calling thread moves to the runnable state.Interruption occurs: If the calling thread is interrupted while waiting, it will move back to the runnable state. The interruption causes the thread to exit the waiting state prematurely.
Example of join()
in Action
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("Child thread executing");
try {
Thread.sleep(1000); // Simulate work
} catch (InterruptedException e) {
System.out.println("Thread interrupted");
}
}
}
}
public class ThreadJoinExample {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread(); // Creating child thread
t.start(); // Start the thread
System.out.println("Main thread before join");
// Main thread waits for child thread to finish using join
t.join(); // Main thread enters the waiting state
// Main thread resumes after child thread finishes execution
System.out.println("Main thread after join");
}
}
Explanation of the Example:
Thread Creation:
- A new thread
t
is created by extendingThread
and overriding therun()
method.
- A new thread
Thread Start:
- The child thread (
t
) starts execution usingt.start()
.
- The child thread (
Main Thread Calling
join()
:- The main thread calls
t.join()
, which causes the main thread to enter the waiting state until threadt
completes.
- The main thread calls
Thread Resumption:
- After the child thread finishes, the main thread resumes execution and prints
"Main thread after join"
.
- After the child thread finishes, the main thread resumes execution and prints
Key Scenarios When Thread Moves from Waiting to Runnable
When
t
finishes execution:- The calling thread will move from the waiting state back to the runnable state when the thread it is waiting for finishes.
When the specified timeout (in
join(long millis)
orjoin(long millis, int nanos)
) expires:- The calling thread will be notified after the timeout and can resume execution, moving to the runnable state.
When the thread is interrupted:
- If the thread is interrupted while waiting, it will exit the waiting state and move to the runnable state. An
InterruptedException
will be thrown.
- If the thread is interrupted while waiting, it will exit the waiting state and move to the runnable state. An
Sleep Example with join()
The sleep()
method is often used in conjunction with join()
to simulate time delays. The calling thread can be interrupted during sleep or while waiting for another thread to finish, leading to state transitions.
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("Child thread executing");
try {
Thread.sleep(1000); // Simulate work
} catch (InterruptedException e) {
System.out.println("Thread interrupted");
}
}
}
}
public class ThreadSleepJoinExample {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start(); // Start child thread
t.join(3000); // Main thread waits for 3 seconds or until child finishes
System.out.println("Main thread resumed after 3 seconds or child completion");
}
}
In this example:
The main thread calls
t.join(3000)
to wait for at most 3 seconds.The child thread is sleeping for 1 second at a time in its
run()
method.The main thread will wait for the child thread for up to 3 seconds, but will resume after that if the child thread hasn't completed.
Conclusion
The join()
method is used to ensure that one thread waits for another thread to finish before continuing its execution. Depending on the variant of join()
used (with or without time limits), the calling thread can enter a waiting state for a specified period or indefinitely. Upon completion of the target thread, timeout, or interruption, the calling thread will move back to the runnable state.
Without timeout (
join()
): The calling thread waits indefinitely until the target thread finishes.With timeout (
join(long millis)
): The calling thread waits for the specified time or until the target thread finishes.With nanosecond precision (
join(long millis, int nanos)
): The calling thread waits for the specified time and nanoseconds.
Understanding how threads move between these states is crucial for writing efficient and bug-free multithreaded applications.
Understanding the sleep()
Method in Java
The sleep()
method in Java is used to pause the execution of the current thread for a specified period of time. It is part of the Thread
class, and it allows you to control the timing and delay of thread execution.
When to Use sleep()
Purpose: If a thread does not need to perform any operation for a particular amount of time, you can use the
sleep()
method to pause its execution.Example Use Case: One common use case is a timer (like in PowerPoint presentations), where after showing a slide, the thread pauses for a few seconds before displaying the next one.
Signatures of the sleep()
Method
public static void sleep(long ms) throws InterruptedException
:This method puts the current thread to sleep for the specified number of milliseconds.
It throws an
InterruptedException
if another thread interrupts the sleeping thread.
public static void sleep(long ms, int ns) throws InterruptedException
:This method puts the current thread to sleep for the specified milliseconds and nanoseconds.
It also throws
InterruptedException
if interrupted.
Important Points to Note About sleep()
Checked Exception: Both versions of
sleep()
throw anInterruptedException
, which is a checked exception. Therefore, it must either be caught using atry-catch
block or declared using thethrows
keyword.Thread States:
New (Born) State: The thread is created but not yet started.
Ready/Runnable State: The thread is ready to run and waiting for CPU allocation.
Running State: The thread is currently executing.
Sleeping State: The thread is in the sleeping state and is not performing any operations during the specified sleep time.
Dead State: The thread has finished execution and can no longer be started.
Thread Flow with sleep()
When a thread calls
sleep()
, it enters the sleeping state for the specified duration.After the time expires or if the thread is interrupted, the thread returns to the ready/runnable state and waits for CPU allocation to continue execution.
Example 1: Using sleep()
in a Sequence
public class Multithreading41 {
public static void main(String[] args) throws InterruptedException {
System.out.println("R");
Thread.sleep(3000); // Sleep for 3 seconds
System.out.println("C");
Thread.sleep(3000); // Sleep for 3 seconds
System.out.println("B");
Thread.sleep(3000); // Sleep for 3 seconds
System.out.println("Kohli");
}
}
Output:
R
C
B
Kohli
Explanation:
The program prints
"R"
, then sleeps for 3 seconds. After that, it prints"C"
, sleeps again, and so on.Each
Thread.sleep(3000)
pauses the execution for 3 seconds, resulting in a delay between the printed statements.
Example 2: Using sleep()
in a Loop
public class Multithreading41 {
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= 10; i++) {
System.out.println("Slide: " + i);
Thread.sleep(2000); // Sleep for 2 seconds
}
}
}
Output:
Slide: 1
Slide: 2
Slide: 3
Slide: 4
Slide: 5
Slide: 6
Slide: 7
Slide: 8
Slide: 9
Slide: 10
Explanation:
The program prints each slide number with a 2-second delay between each print.
The
sleep(2000)
method pauses the program for 2 seconds after printing each slide number, giving a delay between iterations.
Thread Lifecycle with sleep()
Initial State: The thread starts in the ready/runnable state after being created and started.
Sleeping State: When the thread invokes
sleep()
, it enters the sleeping state.Returning to Ready/Runnable: After the sleep time expires (or if interrupted), the thread returns to the ready/runnable state.
Completion: Once the thread finishes execution, it enters the dead state.
Conclusion
The sleep()
method in Java is a useful tool for controlling thread execution, pausing it for a specified amount of time. It is important to handle the InterruptedException
when using sleep()
to avoid compile-time errors. Understanding the thread lifecycle and state transitions (from running to sleeping and back to ready/runnable) is essential for creating efficient and controlled multi-threaded applications.
Life Cycle of a Thread When sleep()
Method is Called
The sleep()
method in Java affects a thread's life cycle by causing it to pause execution for a specified time. Understanding how the thread transitions between states when using sleep()
is crucial for handling multi-threading correctly.
1. Transition to Sleeping State
When a thread invokes the sleep()
method, it moves into the sleeping state. This happens in the following situations:
Using
join()
: When you calljoin()
on a thread, the calling thread (the one that invokesjoin()
) will wait until the other thread completes its execution. If a specific time limit is provided (e.g.,thread.join(1000)
), the calling thread sleeps for that amount of time.Example:
thread.join(1000); // The thread waits for 1 second
Using
sleep()
: The thread explicitly sleeps for a specified amount of time using thesleep()
method.Example:
Thread.sleep(1000); // The thread sleeps for 1000 milliseconds (1 second)
2. Signatures of sleep()
Method
Java provides two variations of the sleep()
method to control how long the thread should pause:
public static void sleep(long ms) throws InterruptedException
This version makes the current thread sleep for a specified number of milliseconds.
It throws
InterruptedException
if the thread is interrupted while sleeping.
public static void sleep(long ms, int ns) throws InterruptedException
This version makes the current thread sleep for a specified milliseconds (
ms
) and nanoseconds (ns
).Again, it throws
InterruptedException
if interrupted.
3. Transition from Sleeping State to Ready/Runnable State
A thread can exit the sleeping state in the following situations:
Time Expiration: The thread wakes up automatically once the specified sleep time has passed.
- Example: If a thread sleeps for 1000 milliseconds (1 second), it will return to the ready/runnable state after that duration, unless interrupted.
Thread Interruption: If another thread interrupts the sleeping thread, it will immediately transition back to the ready/runnable state, even if the sleep time hasn't fully elapsed.
Example:
Thread t = new Thread(() -> { try { Thread.sleep(5000); // Sleep for 5 seconds } catch (InterruptedException e) { System.out.println("Thread was interrupted"); } }); t.start(); t.interrupt(); // Interrupting the thread before it finishes sleeping
Thread Lifecycle with sleep()
Ready/Runnable State: The thread starts in the ready/runnable state after being created and started.
Running State: Once the CPU scheduler picks the thread, it enters the running state and starts executing.
Sleeping State: When
sleep()
is called, the thread transitions to the sleeping state.Returning to Ready/Runnable: After the sleep time expires or the thread is interrupted, it moves back to the ready/runnable state, waiting for the CPU to allocate time to continue execution.
Dead State: Once the thread completes execution, it moves to the dead state and can no longer be started.
Visual Representation
Ready/Runnable state <---> Sleeping state <---> Running state
| | |
|---> sleep() |---> time expires |
| |---> interrupted |
|---> join() with timeout |
The thread starts in the ready/runnable state and begins execution in the running state.
When
sleep()
orjoin()
with a timeout is called, the thread transitions to the sleeping state.After the sleep time expires, or if the thread is interrupted, it returns to the ready/runnable state.
Conclusion
The sleep()
method is essential for pausing the execution of a thread for a specific period. It allows threads to pause and then return to the ready/runnable state once the time expires or if they are interrupted. Understanding how this method interacts with the thread lifecycle helps in creating efficient multi-threaded applications and prevents issues like unnecessary CPU usage or unresponsiveness.
Conclusion for Chapter 40: Multithreading in Java (Part 3)
In this chapter, we've gained a deeper understanding of thread management in Java, focusing on thread priorities, synchronization with the join()
method, and managing thread execution with the sleep()
method. We explored how thread priorities influence scheduling, how to control thread execution order using join()
, and how to pause threads with sleep()
while ensuring efficient multitasking. Through practical examples, we've learned to avoid issues like deadlocks and interruptions, ensuring smoother multithreaded applications. Mastering these concepts equips you with the tools to handle complex concurrency challenges in Java programming.
Other Series:
Connect with Me
Stay updated with my latest posts and projects by following me on social media:
LinkedIn: Connect with me for professional updates and insights.
GitHub: Explore my repository and contributions to various projects.
LeetCode: Check out my coding practice and challenges.
Your feedback and engagement are invaluable. Feel free to reach out with questions, comments, or suggestions. Happy coding!
Rohit Gawande
Full Stack Java Developer | Blogger | Coding Enthusiast