This article is participating in the “Java Theme Month – Java Brush questions card”, activity link

The title

Analyze the LockSupport mechanism from the bottom

knowledge

The introduction of LockSupport

The LockSupport class is a class introduced in Java6(JSR166-JUc) that provides basic thread synchronization primitives. LockSupport actually calls functions in the Unsafe class, which, after all, has just two functions, just two simple interfaces, that provide a powerful synchronization primitive for the upper layer to parse out what the functions do.

public native void unpark(Thread jthread);  
public native void park(boolean isAbsolute, long time);  
Copy the code
  • If the current thread is blocked, the parking space will be occupied. It’s a pretty good name.

    • The isAbsolute parameter specifies whether the time isAbsolute.

    • The time parameter is the time value

The thread calls the park function and waits for permission.

  • Unpark: Unblock the given thread blocked.

    • The thread parameter is used to unblock the corresponding thread.

The thread calls the unpark function to permit the thread.

  • It’s kind of like a semaphore, but the permissions are not superimposed, the permissions are one-time.

  • For example, if thread B calls unpark three times in A row, this “permission” is used when thread A calls park. If thread A calls park again, it enters the wait state.

Note that the unpark function can be called before park. For example, thread B calls unpark and gives thread A A “permission”. When thread A calls park, it finds that it already has A “permission” and immediately resumes running. This part is much better than wait/notify.

Flexibility of Park and Unpark

Unpark functions can be called before park, which is where their flexibility lies.

  • A thread may call park before, or after, or at the same time as another thread unPark, and because of the nature of park, it does not have to worry about the timing of its park. Otherwise, if park has to be before unPark.

Let’s think about what happens when two threads synchronize?

  • In Java5, synchronization is done with wait/notify/notifyAll. One of the annoying aspects of wait/notify is that if thread B wants to notify thread A, thread B must ensure that thread A has waited on the wait call, otherwise thread A may wait forever.

Also, is it notify or notifyAll?

Notify will only wake up one thread, and if there are two threads waiting on the same object by mistake, then again. It seems that notifyAll is the only way to be safe.

The Park/Unpark model really decouples synchronization between threads. Threads no longer need an Object or other variable to store state and no longer need to care about each other’s state.

extends

The park/unpark implementation in HotSpot, where each Java thread has a Parker instance, is defined as follows:

class Parker : public os: :PlatformParker {  
private:  
 volatile int_counter ; .public:  
 void park(bool isAbsolute, jlong time);  
 void unpark(a); . }class PlatformParker : public CHeapObj<mtInternal> {  
 protected:  
   pthread_mutex_t _mutex [1]; pthread_cond_t _cond [1]; . }Copy the code
  • You can see that the Parker class is actually implemented using Posix’s mutex, condition.

  • The _counter field in the Parker class is used to record what is called “permission”.

  • When calling park, first try to get permission directly (_counter>0). If so, set _counter to 0 and return :(very similar to semaphore!)


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.  
  if (Atomic::xchg(0, &_counter) > 0) return;  
 
Copy the code

If not, construct a ThreadBlockInVM and check if _counter is >0. If so, set _counter to 0, unlock mutex and return:


ThreadBlockInVM tbivm(jt);  
if (_counter > 0)  { // no wait needed  
  _counter = 0;  
  status = pthread_mutex_unlock(_mutex);  
 
Copy the code

If not, call pthread_cond_wait and wait. If wait returns, set _counter to 0, unlock mutex and return:

if (time == 0) {  
 status = pthread_cond_wait (_cond, _mutex) ;  
}  
_counter = 0 ;  
status = pthread_mutex_unlock(_mutex) ;  
assert_status(status == 0, status, "invariant"); OrderAccess::fence();Copy the code

When unpark, it’s much easier, just set _counter to 1 and unlock mutext returns. If the value before _counter is 0, the pthread_cond_signal is also called to wake up the threads waiting in the park:

void Parker::unpark() {  
  int s, status ;  
  status = pthread_mutex_lock(_mutex);  
  assert (status == 0."invariant"); s = _counter; _counter =1;  
  if (s < 1) {  
     if (WorkAroundNPTLTimedWaitHang) {  
        status = pthread_cond_signal (_cond) ;  
        assert (status == 0."invariant"); status = pthread_mutex_unlock(_mutex);assert (status == 0."invariant"); }else {  
        status = pthread_mutex_unlock(_mutex);  
        assert (status == 0."invariant"); status = pthread_cond_signal (_cond) ;assert (status == 0."invariant"); }}else {  
    pthread_mutex_unlock(_mutex);  
    assert (status == 0."invariant"); }}Copy the code
  • A _counter variable is protected with mutex and condition, which is set to 0 when park and 1 when unpark.

  • Note that the park function does not use a while to call pthread_cond_wait, so the posiX condition “Spurious wakeup” is also passed to the upper Java code.

if (time == 0) {  
  status = pthread_cond_wait (_cond, _mutex) ;  
}  
Copy the code

This is why Java DOS mentions that the park function returns in three cases:

Some other thread invokes unpark with the current thread as the target; or
Some other thread interrupts the current thread; or
The call spuriously (that is, for no reason) returns.
Copy the code

The relevant implementation code is at:

Hg.openjdk.java.net/build-infra… Hg.openjdk.java.net/build-infra… Hg.openjdk.java.net/build-infra… Hg.openjdk.java.net/build-infra…

A few other things: The Parker class uses a trick to allocate memory by overloading the new function for cache line alignment.

// We use placement-new to force ParkEvent instances to be // aligned on 256-byte address boundaries. This ensures that the least // significant byte of a ParkEvent address is always 0. void * operator new (size_t sz) ; Parker uses an unlocked queue to allocate free Parker instances:Copy the code
volatile int Parker::ListLock = 0 ; Parker * volatile Parker::FreeList = NULL ; Parker * Parker::Allocate (JavaThread * t) { guarantee (t ! = NULL, "invariant") ; Parker * p ; // Start by trying to recycle an existing but unassociated // Parker from the global free list. for (;;) { p = FreeList ; if (p == NULL) break ; // 1: Detach // Tantamount to p = Swap (&FreeList, NULL) if (Atomic::cmpxchg_ptr (NULL, &FreeList, p) ! = p) { continue ; } // We've detached the list. The list in-hand is now // local to this thread. This thread can operate on the // list without risk of interference from other threads. // 2: Extract -- pop the 1st element from the list. Parker * List = p->FreeNext ; if (List == NULL) break ; for (;;) { // 3: Try to reattach the residual list guarantee (List ! = NULL, "invariant") ; Parker * Arv = (Parker *) Atomic::cmpxchg_ptr (List, &FreeList, NULL) ; if (Arv == NULL) break ; // New nodes arrived. Try to detach the recent arrivals. if (Atomic::cmpxchg_ptr (NULL, &FreeList, Arv) ! = Arv) { continue ; } guarantee (Arv ! = NULL, "invariant") ; // 4: Merge Arv into List Parker * Tail = List ; while (Tail->FreeNext ! = NULL) Tail = Tail->FreeNext ; Tail->FreeNext = Arv ; } break ; } if (p ! = NULL) { guarantee (p->AssociatedWith == NULL, "invariant") ; } else { // Do this the hard way -- materialize a new Parker.. // In rare cases an allocating thread might detach // a long list -- installing null into FreeList --and // then stall. Another thread calling Allocate() would see // FreeList == null and then invoke the ctor. In this case we // end up with  more Parkers in circulation than we need, but // the race is rare and the outcome is benign. // Ideally, the # of extant Parkers is equal to the // maximum # of threads that existed at any one time. // Because of the race mentioned above, segments of the // freelist can be transiently inaccessible. At worst // we may end up with the # of Parkers in circulation // slightly above the ideal. p = new Parker() ; } p->AssociatedWith = t ; // Associate p with t p->FreeNext = NULL ; return p ; } void Parker::Release (Parker * p) { if (p == NULL) return ; guarantee (p->AssociatedWith ! = NULL, "invariant") ; guarantee (p->FreeNext == NULL , "invariant") ; p->AssociatedWith = NULL ; for (;;) { // Push p onto FreeList Parker * List = FreeList ; p->FreeNext = List ; if (Atomic::cmpxchg_ptr (p, &FreeList, List) == List) break ; }}Copy the code