Source code analysis of LockSupport(park/unpark)
park method
park()
public static void park() { UNSAFE.park(false, 0L); }
park(Object blocker)
public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null); }
blocker is used to record who blocks a thread when it is blocked. Used for thread monitoring and analysis tools to locate the cause.
The setBlocker(t, blocker) method is used to record that the t thread is blocked by the broker.
Therefore, we only focus on the core method, which is UNSAFE.park(false, 0L).
//park public native void park(boolean isAbsolute, long time); //unpack public native void unpark(Object var1);
UNSAFE is a very powerful class. Its operation is based on the bottom layer, that is, it can directly operate memory,
Underlying implementation principle of Unsafe.park
Under Linux system, it is implemented by mutex and condition in Posix thread library pthread.
mutex and condition protect a_ The variable of counter is set to 0 when it is park and 1 when it is unpark.
_ The counter field is used to record the so-called "license"
The condition variable is used to block a thread. When the condition is not satisfied, the thread often unlocks the corresponding mutex and waits for the condition to change. Once another thread changes the condition variable, it will notify the corresponding condition variable to wake up one or more threads blocked by this condition variable. These threads will re lock the mutex and retest whether the condition is met.
Each Java thread has a Parker instance. The Parker class is defined as follows:
park.hpp
/* In the future we'll want to think about eliminating Parker and using * ParkEvent instead. There's considerable duplication between the two * services. */ /* *In the future, we will consider getting rid of Parker and replacing it with ParkEvent. There are quite a lot of duplicate services between the two. */ class Parker : public os::PlatformParker { private: volatile int _counter ; Parker * FreeNext ; JavaThread * AssociatedWith ; // Current association public: Parker() : PlatformParker() { _counter = 0 ; FreeNext = NULL ; AssociatedWith = NULL ; } protected: ~Parker() { ShouldNotReachHere(); } public: // For simplicity of interface with Java, all forms of park (indefinite, // relative, and absolute) are multiplexed into one call. void park(bool isAbsolute, jlong time); void unpark(); // Lifecycle operators static Parker * Allocate (JavaThread * t) ; static void Release (Parker * e) ; private: static Parker * volatile FreeList ; static volatile int ListLock ; };
os::PlatformParker is implemented by the operating system, such as linux
os_linux.hpp
class PlatformParker : public CHeapObj<mtInternal> { protected: enum { REL_INDEX = 0, ABS_INDEX = 1 }; int _cur_index; // which cond is in use: -1, 0, 1 pthread_mutex_t _mutex [1] ; pthread_cond_t _cond [2] ; // one for relative times and one for abs. public: // TODO-FIXME: make dtor private ~PlatformParker() { guarantee (0, "invariant") ; } public: PlatformParker() { int status; status = pthread_cond_init (&_cond[REL_INDEX], os::Linux::condAttr()); assert_status(status == 0, status, "cond_init rel"); status = pthread_cond_init (&_cond[ABS_INDEX], NULL); assert_status(status == 0, status, "cond_init abs"); status = pthread_mutex_init (_mutex, NULL); assert_status(status == 0, status, "mutex_init"); _cur_index = -1; // mark as unused } };
① When calling park, first try to get the "license" directly, that is_ When counter > 0, if successful, the_ Set counter to 0 and return.
② If not, construct a ThreadBlockInVM and check_ Is counter > 0? If yes, set_ Set counter to 0, unlock mutex and return:
③ Otherwise, judge the waiting time, and then call pthread_ cond_ The wait function waits. If the wait returns, the_ Set counter to 0, unlock mutex and return:
os_linux.cpp
void Parker::park(bool isAbsolute, jlong time) { // Ideally we'd do something useful while spinning, such // as calling unpackTime(). // Optional fast-path check: // Return immediately if a permit is available. // We depend on Atomic::xchg() having full barrier semantics // since we are doing a lock-free update to _counter. //① First try whether you can get the "license" directly if (Atomic::xchg(0, &_counter) > 0) return; Thread* thread = Thread::current(); assert(thread->is_Java_thread(), "Must be JavaThread"); JavaThread *jt = (JavaThread *)thread; // Optional optimization -- avoid state transitions if there's an interrupt pending. // Check interrupt before trying to wait if (Thread::is_interrupted(thread, false)) { return;//If interrupted by interrupt, wake up and return } // Next, demultiplex/decode time arguments timespec absTime; if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all //When the time is up, or represents the absolute time. At the same time, the absolute time is 0 (it is also time at this time). It is returned directly. The parkUtil in java transmits the absolute time, and others are not return; } if (time > 0) { //The time parameter is passed in, stored in absTime, and parsed into absTime - > TV_ SEC and absTime - > TV_ Nsec (nanosecond) stores absolute time unpackTime(&absTime, isAbsolute, time); } // Enter safepoint region // Beware of deadlocks such as 6317397. // The per-thread Parker:: mutex is a classic leaf-lock. // In particular a thread must never block on the Threads_lock while // holding the Parker:: mutex. If safepoints are pending both the // the ThreadBlockInVM() CTOR and DTOR may grab Threads_lock. //② Construct a ThreadBlockInVM and check it_ Is counter > 0? If yes, set_ Set counter to 0, unlock mutex and return: //Enter the safepoint region and change the thread to the blocking state ThreadBlockInVM tbivm(jt); // Don't wait if cannot get lock since interference arises from // unblocking. Also. check interrupt before trying wait if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) { //If the thread is interrupted or fails to lock the mutex variable in the process of trying to lock it, for example, it is locked by other threads, it returns directly return; } //This indicates that the thread mutex locking is successful int status ; //The license is 1 and no waiting is required if (_counter > 0) { // no wait needed _counter = 0;//There is already a license. Use the current license //Unlock mutually exclusive variables status = pthread_mutex_unlock(_mutex); assert (status == 0, "invariant") ; // Paranoia to ensure our locked and lock-free paths interact // correctly with each other and Java-level accesses. //Use memory barriers to ensure_ If counter is set to 0 (write operation), the previous result of the memory barrier can be obtained by the read operation after the memory barrier, that is, 0 can be read correctly OrderAccess::fence(); return;//return } #ifdef ASSERT // Don't catch signals while blocked; let the running threads have the signals. // (This allows a debugger to break into the running thread.) sigset_t oldsigs; sigset_t* allowdebug_blocked = os::Linux::allowdebug_blocked_signals(); pthread_sigmask(SIG_BLOCK, allowdebug_blocked, &oldsigs); #endif //Set the operating system thread owned by the java thread to CONDVAR_WAIT status, indicating that you are waiting for a condition to occur OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */); jt->set_suspend_equivalent(); // cleared by handle_special_suspend_equivalent_condition() or java_suspend_self() assert(_cur_index == -1, "invariant"); if (time == 0) { _cur_index = REL_INDEX; // arbitrary choice when not timed //③ Determine the waiting time, and then call pthread_ cond_ The wait function waits. If the wait returns, the_ Set counter to 0, unlock mutex and return: //Put the calling thread on the thread list of the waiting condition, and then unlock the mutex variable (these two are atomic operations). At this time, the thread enters the waiting. When it returns, the mutex variable is locked again. //0 is returned successfully, otherwise the error number is returned status = pthread_cond_wait (&_cond[_cur_index], _mutex) ; } else { _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX; status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ; if (status != 0 && WorkAroundNPTLTimedWaitHang) { pthread_cond_destroy (&_cond[_cur_index]) ; pthread_cond_init (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr()); } } _cur_index = -1; assert_status(status == 0 || status == EINTR || status == ETIME || status == ETIMEDOUT, status, "cond_timedwait"); #ifdef ASSERT pthread_sigmask(SIG_SETMASK, &oldsigs, NULL); #endif //After waiting, the license is consumed and changed to 0_ counter = 0 ; //Release lock of mutex _counter = 0 ; status = pthread_mutex_unlock(_mutex) ; assert_status(status == 0, status, "invariant") ; // Paranoia to ensure our locked and lock-free paths interact // correctly with each other and Java-level accesses. //Add memory barrier instruction OrderAccess::fence(); // If externally suspended while waiting, re-suspend if (jt->handle_special_suspend_equivalent_condition()) { jt->java_suspend_self(); } }
ThreadBlockInVM tbivm(jt)
This belongs to the syntax of creating a new variable in C + +, that is, calling the constructor to create a new variable, the variable name is tbivm, and the parameter is jt. Class is implemented as
class ThreadBlockInVM : public ThreadStateTransition { public: ThreadBlockInVM(JavaThread *thread) : ThreadStateTransition(thread) { // Once we are blocked vm expects stack to be walkable thread->frame_anchor()->make_walkable(thread); //Turn the thread from running state to blocking state trans_and_fence(_thread_in_vm, _thread_blocked); } ... };
_ thread_in_vm indicates that the thread is currently executing in VM_ thread_blocked indicates that the thread is currently blocked. They are enumerations defined in globalDefinitions.hpp
//This enumeration is used to track the execution of threads in the code. It is used for safepoint code. There are four important types_ thread_new/_thread_in_native/_thread_in_vm/_thread_in_Java. Shaped like xxx_ The trans states are all intermediate states, indicating that the thread is changing from one state to another. This way makes the safepoint code do not need to suspend the thread when processing the thread state, making the safe point code run faster. Given a state, its transition state can be obtained by + 1 enum JavaThreadState { _thread_uninitialized = 0, // should never happen (missing initialization) _thread_new = 2, // just starting up, i.e., in process of being initialized _thread_new_trans = 3, // corresponding transition state (not used, included for completeness) _thread_in_native = 4, // running in native code . This is a safepoint region, since all oops will be in jobject handles _thread_in_native_trans = 5, // corresponding transition state _thread_in_vm = 6, // running in VM _thread_in_vm_trans = 7, // corresponding transition state _thread_in_Java = 8, // Executing either interpreted or compiled Java code running in Java or in stub code _thread_in_Java_trans = 9, // corresponding transition state (not used, included for completeness) _thread_blocked = 10, // blocked in vm _thread_blocked_trans = 11, // corresponding transition state _thread_max_state = 12 // maximum thread state+1 - used for statistics allocation };
This is the process of the whole park. To sum up, it is the process of consuming "licenses".
unpark
/** * If the license for the given thread is not yet available, make it available. * If a thread is blocked on a park, it will unblock it. * Otherwise, ensure that the next call to park will not be blocked. * If the given thread has not been started, there is no guarantee that this operation will have any effect. * @param thread: The thread to perform the unpark operation; A null parameter indicates that this operation has no effect. */ public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); }
Unsafe.class
public native void unpark(Object var1);
Underlying implementation principle of Unsafe.unpark
When unpark is used, it is much simpler and can be set directly_ Counter is 1, and then unlock mutext returns. If_ If the value before counter is 0, pthread will also be called_ cond_ Signal wakes up the thread waiting in the park:
os_linux.cpp
void Parker::unpark() { int s, status ; //Lock the mutex. If the mutex is locked, it will be blocked until the mutex is unlocked //When park enters the wait_ mutex will be released status = pthread_mutex_lock(_mutex); assert (status == 0, "invariant") ; s = _counter;Store old_counter permit _counter = 1;//Set license to 1 if (s < 1) {//s=0 blocked by park // thread might be parked if (_cur_index != -1) { // thread is definitely parked if (WorkAroundNPTLTimedWaitHang) { status = pthread_cond_signal (&_cond[_cur_index]);//Wake up the thread waiting in the park assert (status == 0, "invariant"); status = pthread_mutex_unlock(_mutex);//Release lock assert (status == 0, "invariant"); } else { status = pthread_mutex_unlock(_mutex);//Release lock assert (status == 0, "invariant"); status = pthread_cond_signal (&_cond[_cur_index]);//Wake up the thread waiting in the park assert (status == 0, "invariant"); } } else { pthread_mutex_unlock(_mutex);//Release lock assert (status == 0, "invariant") ; } } else { //If you always have permission, release the lock you added, and if you have permission, the park itself will return pthread_mutex_unlock(_mutex);//Release lock assert (status == 0, "invariant") ; } }
The function of unpark itself is to issue licenses and notify the waiting thread that the waiting can be ended
Source code interpretation
Make an analogy
Thread is like a traveler, Parker is like his backpack_ cond condition variable is like a tent in a backpack, providing a place to rest_ counter is like the spare dry food in the backpack (0 is exhausted and 1 is sufficient)
Call park to see if you need to stop and rest
If the spare dry food runs out, get into the tent and rest
If there is enough spare dry food, don't stop and move on
Calling unpark is like replenishing dry food to make it sufficient
If the thread is still in the tent, wake up and let him move on
If the thread is still running at this time, the next time he calls park, he will only consume the spare dry food without staying and moving on
Because the backpack space is limited, calling unpark multiple times will only supplement one spare dry food
Call park()
-
The current thread calls the Unsafe.park() method
-
Check_ counter, if the case is 0, then the_ Mutex mutex
-
Thread entry_ cond condition variable, enter the blocking state and release the mutex
-
To be on the safe side, set it again_ counter = 0
Call unpark()
-
Call the Unsafe.unpark(Thread_0) method to set_ counter is 1
-
Awaken_ Thread in cond condition variable_ 0
-
Thread_0 resume operation
-
Consumer license, setting_ counter is 0
First call unpark(), then call park().
-
Call the Unsafe.unpark(Thread_0) method to set_ counter is 1
-
The current thread calls the Unsafe.park() method
-
Check_ counter, this case is 1. At this time, the thread does not need to be blocked and continues to run
-
Set_ counter is 0