LockSupport

java.util.concurrent.locks.LockSupport

private static final sun.misc.Unsafe UNSAFE;
static {
    try {
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> tk = Thread.class;
        parkBlockerOffset = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("parkBlocker"));
        SEED = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomSeed"));
        PROBE = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomProbe"));
        SECONDARY = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
    } catch (Exception ex) { throw new Error(ex); }
}

sun.misc.Unsafe#getUnsafe

public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

The unpark function provides a "permit" for the thread, and the thread calls the park function to wait for the "permit"

This is a bit like a semaphore, but this "permission" cannot be superimposed, and the "permission" is one-time

Note that the unpark function can be called before park. For example, thread B calls the unpark function and sends a "permit" to thread A, then when thread A calls park, it finds that there is already a "permission", then it will continue to run immediately

这一点,也恰巧是park/unpark的灵活的地方。可以解决多种场景应用。

A bit similar, a semaphore with a capacity of 1

A thread may call park before, after, or at the same time as other threads unPark, so because of the characteristics of park, it does not need to worry about the timing of its own park. Otherwise, if park must be before unpark, then give Programming brings a lot of trouble! !

In the Java thread object (the corresponding Java thread object in vm, C++), there is a Parker class

class Parker : public os::PlatformParker {  
private:  
  volatile int _counter ;  
  ...  
public:  
  void park(bool isAbsolute, jlong time);  
  void unpark();  
  ...  
}  
class PlatformParker : public CHeapObj<mtInternal> {  
  protected:  
    pthread_mutex_t _mutex [1] ;  
    pthread_cond_t  _cond  [1] ;  
    ...  
}
  • pthread_mutex_t
    • Posix的mutex
  • pthread_cond_t
    • Posix condition
  • _counter
    • volatile
    • Record the number of "licenses"
    • At the parktime, try to get the "permission" directly
      • _counter > 0, then update to 0 and return
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;  

Take advantage of atomic updates

If unsuccessful:

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

Then update _counterto 0 and add a mutex

Then you need to determine whether you need to wait

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

Use pthread_cond_waitto wait. After pthread_mutex_unlockthe assertion checks the status

When unparking, if it _counteris 1, release the mutex and put it back. If it is 0, it means that the thread was occupying the "permission" before. Then it is possible that other threads are waiting. So at 0, it is necessary to additionally wake up the waiting threadpthread_cond_signal

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") ;  
  }  
}  

In short, a _counter variable is protected with mutex and condition. When parked, this variable is set to 0, and when unparked, this variable is set to 1

It is worth noting that in the park function, when calling pthread_cond_wait, it does not use while to judge, so the "Spurious wakeup" in the posix condition will also be passed to the upper-level Java code.

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

in the Java documentation

  • 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.

Mutex and condition are actually bound together, and a condition can only correspond to one mutex. In Java code, the Condition object can only be obtained through the lock.newCondition() function.

  • Pseudo wakeup
    • Spurious wakeup

The so-called spurious wakeup refers to a thread calling pthread_cond_signal(), but more than one thread may be woken up. Why does this happen? The wiki and some other documentation just say that in the case of multiple cores, the simplified implementation allows for this spurious wakeup

Unlock first and then signal. This has the advantage that the thread calling enqueue_msg can participate in the mutex competition again, which means that multiple messages can be placed in a row, which may improve efficiency. Similar to the unfair mode of ReentrantLock in Java. Some articles on the Internet say that, first singal and then unlock, there may be a situation that the thread awakened by singal will sleep again because it cannot get the mutex immediately (has not been released), which affects the efficiency.

There will be an optimization called "wait morphing", that is, if the thread is awakened but cannot acquire the mutex, the thread is transferred (morphing) to the mutex's waiting queue.

Quote:

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325696253&siteId=291194637