Table of Contents
概念
- 許可:一種類似信號量((java.util.concurrent.Semaphore)的技術,用來標記當前線程是否允許阻塞。但是不同於信號量可以設置多個信號標識,許可只有一個是否有效的標識。如果許可是有效的,可以理解爲當前線程已經被阻塞;如果許可無效,那麼可以理解線程未被阻塞。
作用
LockSupport 是一個線程同步工具,提供線程的阻塞和取消線程阻塞的功能。LockSupport 的阻塞和取消阻塞的功能是通過類似信號量(java.util.concurrent.Semaphore)技術的許可(permit)來實現的。如果調用阻塞方法 LockSupport.park()時,許可有效,那麼該方法會立刻返回,否則該方法可能會阻塞。
Thread 本身也有類似的方法實現線程阻塞(Thread.suspend)和喚醒(Thread.resume),但是這兩個方法提供的線程阻塞和取消阻塞的方式有死鎖的風險,所以已經被棄用了。LockSupport 提供 park 和 unpark 方法是替換 suspend 和 resume 方法的很好的方式。
除了提供無參數的 park 方法,LockSupport 還提供了帶時間參數的 park 方法,用於設置等待時間。
另外 unpark 可以先於 park 方法調用,增加了使用了靈活性。
Park 方法的實現
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
java.lang.Thread 類有一個屬性:volatile Object parkBlocker,當我們調用 park 方法時,會使用 UNSAFE.putObject(t, parkBlockerOffset, arg)對線程的 parkBlocker 屬性設值。
我們可以通過調用 java.util.concurrent.locks.LockSupport.getBlocker 的方式獲取線程阻塞的原因。
LockSupport.park 方法是通過調用底層的 UNSAFE.park(false, 0L)實現線程阻塞的。我們繼續分析 UNSAFE.park 是如何實現的。UNSAFE.park 源碼如下:
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.
//Parker 內置了一個_counter 字段,用來記錄許可。如果當_counter>0 時,表示線程可以獲得許可;否則不可以獲得許可。即當調用 park 方法時,如果_counter>0,那麼將_counter 設置爲 0 並返回;
if (Atomic::xchg(0, &_counter) > 0) return;
//獲取當前線程,並確認是 Java 線程
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
//如果當前線程已經被中斷,那麼 park 方法也直接返回。
if (Thread::is_interrupted(thread, false)) {
return;
}
// Next, demultiplex/decode time arguments
//如果等待的時間已經到了,那麼直接返回
timespec absTime;
if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all
return;
}
if (time > 0) {
//將時間轉成秒和納秒存儲起來
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.
//將當前線程更新程阻塞狀態
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) {
return;
}
int status ;
if (_counter > 0) { // no wait needed
//將 park 內最的_counter 設置爲 0,表示許可已經被使用。
_counter = 0;
//釋放鎖
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.
//增加一個內存屏障,確保線程安全
OrderAccess::fence();
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
//將 java 線程所擁有的操作系統線程設置成 CONDVAR_WAIT 狀態
OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
jt->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()
if (time == 0) {
//線程設置爲等待狀態。等待的信息是_cond(unpark 方法將會釋放這個信號)
status = pthread_cond_wait (_cond, _mutex) ;
} else {
status = os::Linux::safe_cond_timedwait (_cond, _mutex, &absTime) ;
if (status != 0 && WorkAroundNPTLTimedWaitHang) {
pthread_cond_destroy (_cond) ;
pthread_cond_init (_cond, NULL);
}
}
assert_status(status == 0 || status == EINTR ||
status == ETIME || status == ETIMEDOUT,
status, "cond_timedwait");
#ifdef ASSERT
pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
#endif
_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.
OrderAccess::fence();
// If externally suspended while waiting, re-suspend
if (jt->handle_special_suspend_equivalent_condition()) {
jt->java_suspend_self();
}
}
如代碼中顯示,Parker 內置了一個_counter 字段,用來記錄許可。如果當_counter >0 時,表示線程可以獲得許可;否則不可以獲得許可。即當調用 park 方法時,如果_counter >0,那麼將_counter 設置爲 0 並返回。
park 線程等待和喚醒是通過調用 POSIX 的線程 API,如 pthread_mutex_trylock,pthread_mutex_unlock,pthread_cond_wait,pthread_cond_destroy,pthread_cond_init 等,確保_counter 變量的線程安全和調用操作系統的線程等待和喚醒。unpark 也是一樣。
UnPark 方法的實現
源碼如下:
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
hotspot 源碼如下:
void Parker::unpark() {
int s, status ;
//添加互斥鎖
status = pthread_mutex_lock(_mutex);
assert (status == 0, "invariant") ;
s = _counter;
//將_counter 的值設置爲 1。表示有許可可用
_counter = 1;
if (s < 1) {
if (WorkAroundNPTLTimedWaitHang) {
//發送_cond 信號。park 方法中線程會阻塞等待這個信號纔會喚醒。
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") ;
}
}