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
park
time, 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 _counter
to 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_wait
to wait. After pthread_mutex_unlock
the assertion checks the status
When unparking, if it _counter
is 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: