Cannot call start on a running or suspended thread lỗi năm 2024

Java threads can be in 6 different states: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED. When a thread is suspended [i.e., unable to progress further], it will be one of these 3 suspended thread states: BLOCKED, WAITING, TIMED_WAITING. Let’s discuss these 3 states with real-life fun examples 🙂

A thread enters the ‘BLOCKED’ state when it’s trying to access a resource that’s currently held by another thread. In this state, the blocked thread cannot proceed until the resource it needs becomes available.

When will a thread enter into a BLOCKED state?

A Thread will enter in to ‘BLOCKED’ state in one of the following scenarios:

1. Synchronized method: Threads enter the ‘BLOCKED’ state when they contend for access to synchronized method. Let’s consider this sample code:

public synchronized void getData[] {
:
:
}

Here the ‘getData[]‘ method is defined as a ‘synchronized’ method. When a method is marked with ‘synchronized’ keyword, JVM will allow only one thread to enter into that ‘synchronized’ method at any given time. If any other tries to invoke this ‘synchronized’ method when some other is already executing this method, it will be put into the ‘BLOCKED’ state. If this synchronized method happens to be in a critical code path, then there is a high possibility for multiple threads to enter into this ‘BLOCKED’ state. In general it’s a good practice to avoid synchronized methods as much as possible for better performance.

2. Synchronized block: When multiple threads contend for access to a synchronized block, only one thread can execute the block at a time. If a thread tries to enter a synchronized block that is already locked by another thread, it goes into the ‘BLOCKED’ state and waits for the lock to be released.

synchronized [sharedLock] {
// Critical section
}

3. ReentrantLock: If a thread attempts to acquire a ReentrantLock that is already held by another thread, it will not immediately get the lock. Instead, it will go into the ‘BLOCKED’ state and wait until the lock becomes available. Once the lock is released by the owning thread [the thread that currently holds the lock], one of the blocked threads will be chosen to acquire the lock and transition from the ‘BLOCKED’ state to the ‘RUNNABLE’ state. The thread that successfully acquires the lock will proceed to execute the locked critical section.

ReentrantLock lock = new ReentrantLock[];
lock.lock[]; // Acquires the lock
// ...
lock.unlock[]; // Releases the lock

How to diagnose BLOCKED state threads?

When threads enter the ‘BLOCKED’ state, it’s essential to identify the specific lines of code or methods causing this behavior. To diagnose such issues non-intrusively, the recommended approach is thread dump analysis. Tools like fastThread and yCrash are invaluable for this purpose. yCrash provides an insightful transitive dependency graph of ‘BLOCKED’ threads, aiding in understanding the thread contention. The graph displays the following key information:

Blocked threads: Threads currently in the ‘BLOCKED’ state.

Blocking thread: Culprit Thread that caused the other threads to enter into the ‘BLOCKED’ state.

Stack traces: Stack trace of both blocking thread and ‘BLOCKED’ thread.

Fig: Transitive dependency graph showing BLOCKED threads generated by yCrash

Gain insights into the root causes of thread contention and ‘BLOCKED’ states by utilizing these tools and techniques, enabling you to optimize your application’s performance and concurrency.

Real-life example

Say, today you are going for a job interview. This is your dream job, which you have been targeting for the last few years. You woke up early in the morning, got ready, put on your best outfit, looking sharp in front of the mirror. Now you step out to your garage and realize that your wife has already taken the car. In this scenario, you only have one car, so what will happen? In real life, a fight may happen :-]. Here you are ‘BLOCKED’ because your wife has already taken the car. You won’t be able to go to the interview. This is the ‘BLOCKED’ state. Explaining it in technical terms, you are the thread T1 and your wife is the thread T2 and lock is the car. T1 is ‘BLOCKED’ on the lock [i.e. the car], because T2 has already acquired this lock.

WAITING

A thread enters the ‘WAITING’ state when it’s waiting for a specific condition to be satisfied, such as a signal from another thread. Threads in the ‘WAITING’ state don’t consume CPU resources and remain inactive until they are explicitly awakened by another thread.

When will a thread enter into a WAITING state?

A Thread will enter into a ‘WAITING’ state when it’s calling one of the following methods:

1. Object

wait[] with no timeout: A thread enters the ‘WAITING’ state when it calls the wait[] method on an object. This method is typically used for synchronization and coordination between threads. The thread will remain in the ‘WAITING’ state until another thread calls the notify[] or notifyAll[] method on the same object.

2. Thread

join[] with no timeout: Thread.join[] method in Java is employed to make a calling thread wait until another specified thread finishes its execution. When a thread invokes join[] on a target thread, the caller enters a ‘WAITING’ state until the target thread completes its task. This mechanism is useful for coordinating the execution of multiple threads, ensuring that a particular thread’s work is finished before proceeding with subsequent operations. Once the target thread finishes executing, the calling thread transitions back to its normal state and continues its own execution. This synchronization approach is valuable in scenarios where you need to manage the order or dependencies of thread execution.

3. LockSupport.park[]: Invoking LockSupport.park[] in Java induces a thread to transition into the ‘WAITING’ state. This method provides a fine-grained synchronization mechanism where a thread voluntarily relinquishes its CPU time and remains in a waiting state until it receives an explicit signal to continue, typically through the unpark[] method. This approach is often utilized in more complex concurrency scenarios to intricately coordinate thread activities, enabling threads to synchronize and communicate with precision according to the needs of the application.

4. CyclicBarrier.await[]: Threads waiting at a CyclicBarrier using the await[] method enter the ‘WAITING’ state until the required number of threads arrive.

5. CountDownLatch.await[]: Threads waiting on a CountDownLatch using the await[] method enter the “WAITING” state until the latch’s count reaches zero.

Real-life example

Let’s say a few minutes later your wife comes back home with the car. Now you realize that the interview time is approaching, and there is a long distance to drive to get there. So, you put all the power on the gas pedal in the car. You drive at 100 mph when the allowed speed limit is only 60 mph. Your luck, a traffic cop sees you driving over the speed limit, and he pulls you over to the curb. Now you are entering into the WAITING state, my friend. You stop driving the car and sit idly in the car until the cop investigates you, and then lets you go. Basically, until he lets you go, you are stuck in the WAITING state. Explaining it in technical terms, you are thread T1 and the cop is thread T2. You released your lock [i.e. you stopped driving the car], and went into the WAITING state. Until the cop [i.e. T2] lets you go, you will be stuck in this WAITING state.

TIMED_WAITING

Threads enter the TIMED_WAITING state when they are waiting for a specific amount of time. This state is often used when a thread needs to wait for a resource but doesn’t want to wait indefinitely. After the specified time elapses, the thread can either proceed if the resource is available or transition to another state.

When will a thread enter into a TIMED_WAITING state?

Here’s a list of methods in Java that can cause a thread to enter the ‘TIMED_WAITING’ state: 1. Thread.sleep[long millis]: Pauses the current thread’s execution for a specified duration of time. 2. Object.wait[long timeout]: Makes a thread wait for a specified duration or until another thread calls notify[] or notifyAll[] on the same object, whichever comes first. 3. LockSupport.parkNanos[long nanos]: Similar to LockSupport.park[], but allows you to specify a time in nanoseconds for the thread to be parked. 4. LockSupport.parkUntil[long deadline]: Parks the current thread until a given deadline, specified in milliseconds since the epoch. 5. Condition.await[long time, TimeUnit unit]: Used with the java.util.concurrent.locks.Condition interface to wait for a condition to be met with a specified timeout. 6. Thread.join[long millis]: Waits for a thread to finish execution for a specified duration of time. It’s important to note that the “TIMED_WAITING” state indicates that a thread is waiting for a specified amount of time before it can continue its execution.

Real-life example

Despite all the drama, you did extremely well in the interview, impressed everyone and got this high paying job. [Congratulations!] You come back home and tell your neighbor about this new job and how excited you are about it. Your friend says that he is also working in the same office building. He suggests that the two of you should drive together. You think it’s a great idea. So on the first day of work, you go to his house. You stop your car in front of his house. You wait for 10 minutes, but your neighbor still doesn’t come out. You go ahead and start driving to work, as you don’t want to be delayed on your first day. Now this is TIMED_WAITING. Explaining it in technical terms, you are thread T1 and your neighbor is thread T2. You release the lock [i.e. stop driving the car] and wait up to 10 minutes. If your neighbor, T2, doesn’t come out in 10 minutes, you start driving the car again.

Video

This 5-minute video covers the BLOCKED, WAITING, and TIMED_WAITING thread states all through the illustrative example of a job interview that we have discussed in this post.

Conclusion

In the realm of Java threading, the states of BLOCKED, WAITING, and TIMED_WAITING emerge as intriguing suspensions where threads momentarily pause their journey. By paralleling these states with real-life scenarios, we’ve uncovered their essence: exclusive resource access, patient anticipation, and timed pauses. Understanding these states empowers us to construct efficient multithreaded applications. Just as life’s moments of waiting lead to progress, these states guide threads to execution’s completion. As you navigate the dynamic landscape of programming, remember these states’ significance, and thread confidently toward concurrency mastery.

Chủ Đề