轉載請註明原創出處,謝謝!html
HappyFeet的博客java
最近在忙着找工做、找房子,事兒也挺多的,加上又換了個城市,也就沒什麼心思寫博客了。現在工做已定,房子也租好了,是時候調整好本身的心態,開始寫博客了。linux
說實話,這段時間面了很多公司,和不少面試官交流了許多,感觸頗多,不過目前還沒想好怎麼寫,我會盡快將這段時間辭職及面試的體會整理成一篇博客發表出來,還請你們耐心等待!c++
暫且把面試的事擱下,我們今天來聊 LockSupport.park() 和 LockSupport.unpark() 的底層原理。git
爲何會聊到這兩個方法呢?緣由是在閱讀 AQS 的源碼的時候發現這兩個方法調用的次數很是多!因此在繼續深刻閱讀 AQS 源碼以前,先來熟悉一下 LockSupport.park() 和 LockSupport.unpark() 的底層實現,爲後續 AQS 的學習打下基礎。github
park 翻譯成中文是 "停放" 的意思,在代碼中的該方法含義是 "掛起" 當前線程。面試
實際上,LockSupport 類中所提供的方法有許多,經常使用的有下面這幾個:api
其中 unpark 用於喚醒線程,其餘三個均用於掛起線程。bash
掛起線程又分爲無限期和有限期掛起,對應到線程的狀態是 WAITING(無限期等待)和 TIMED_WAITING(限期等待)。oracle
一個被無限期掛起的線程恢復的方式有三種:
其餘線程調用了 unpark 方法,參數爲被掛起的線程
其餘線程中斷了被掛起的線程
The call spuriously (that is, for no reason) returns.
這裏講的是虛假喚醒,能夠參考如下幾篇資料:
因爲虛假喚醒的存在,在調用 park 時通常採用自旋的方式,僞代碼以下:
while (!canProceed()) { ... LockSupport.park(this); } 複製代碼
而有限期掛起的除了上面三種以外,還有第四種方式:
This class associates, with each thread that uses it, a permit.
複製代碼
Java 文檔裏說到了,每一個線程都關聯一個許可(permit)。
當許可可用時,調用 park 會當即返回,不然可能(虛假喚醒則不會被掛起)被掛起;若是許可不可用時,調用 unpark 會使得許可變成可用,而若是許可自己是可用時,調用 unpark 不會有任何影響。
可能直接看文字不是那麼的一目瞭然,咱們來看幾個例子:
public static void exampleOne() {
Thread thread = new Thread(() -> {
while (flag) {
}
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park();
System.out.println("after second park");
});
thread.start();
flag = false;
sleep(20);
System.out.println("before unpark.");
LockSupport.unpark(thread);
}
複製代碼
輸出結果
before first park
before unpark
after first park
複製代碼
分析
首先,許可初始是不可用的;因此在調用 park 後 thread 被掛起,後續主線程調用了 unpark 方法喚醒了被掛起的 thread,輸出 after first park
,緊接着 thread 調用 park 繼續被掛起。
private static void exampleTwo() {
Thread thread = new Thread(() -> {
while (flag) {
}
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park();
System.out.println("after second park");
});
thread.start();
LockSupport.unpark(thread);
LockSupport.unpark(thread);
flag = false;
}
複製代碼
輸出結果
before first park
after first park
複製代碼
分析
主線程先對 thread 執行兩次 unpark 操做,而後 thread 再連續調用兩次 park 方法,結果發現,第二個 park 會掛起 thread;這裏主要體現了 unpark 效果不會被累積,當許可可用時,調用 unpark 方法不會產生任何效果。
private static void exampleThree() {
Thread thread = new Thread(() -> {
System.out.println("before first park");
LockSupport.park();
System.out.println("after first park");
LockSupport.park();
System.out.println("after second park");
System.out.println("isInterrupted is " + Thread.interrupted());
System.out.println("isInterrupted is " + Thread.interrupted());
LockSupport.park();
System.out.println("after third park");
});
thread.start();
sleep(200);
thread.interrupt();
}
複製代碼
輸出結果
before first park
after first park
after second park
isInterrupted is true
isInterrupted is false
複製代碼
分析
thread 前後共調用了三次 park,前兩次調用沒啥區別,在第三次調用以前調用了兩次 Thread.interrupted();從輸出結果來看,發現前兩次 park 並無生效,只有第三次 park 將線程掛起了,Why?
咱們先來看 Thread.interrupted() 的做用:**判斷當前線程的中斷狀態,同時將中斷狀態清除。**實際上這裏只須要調用一次 Thread.interrupted() 便可,調用了兩次是爲了查看線程中斷狀態的變化。
當線程的中斷狀態爲 true 時,park 失去了效果,不會掛起線程;而當調用了 Thread.interrupted() 將中斷狀態清除以後,park 又恢復了效果。
因此這裏能夠得出的結論:線程中斷會使 park 失效。
重頭戲來啦!上面講到了 park 和 unpark 的幾個方法,其實它們最終對應於 UNSAFE.park(boolean isAbsolute, long time) 和 UNSAFE.unpark(Object thread) 這兩個 native 方法。
下面咱們就去看看這兩個 native 方法是如何實現的。
unsafe.cpp#Unsafe_Park
:openjdk/hotspot/src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time))
UnsafeWrapper("Unsafe_Park");
EventThreadPark event;
#ifndef USDT2
HS_DTRACE_PROBE3(hotspot, thread__park__begin, thread->parker(), (int) isAbsolute, time);
#else /* USDT2 */
HOTSPOT_THREAD_PARK_BEGIN(
(uintptr_t) thread->parker(), (int) isAbsolute, time);
#endif /* USDT2 */
JavaThreadParkedState jtps(thread, time != 0);
thread->parker()->park(isAbsolute != 0, time);
#ifndef USDT2
HS_DTRACE_PROBE1(hotspot, thread__park__end, thread->parker());
#else /* USDT2 */
HOTSPOT_THREAD_PARK_END(
(uintptr_t) thread->parker());
#endif /* USDT2 */
...
UNSAFE_END
複製代碼
這裏有幾個分支判斷,不過能夠看出不管是哪一個分支,最終都會走 park(bool isAbsolute, jlong time)
這個方法。
os_linux.cpp#Parker::park
:openjdk/hotspot/src/os/linux/vm/os_linux.cpp
void Parker::park(bool isAbsolute, jlong time) {
...
if (Atomic::xchg(0, &_counter) > 0) return;
Thread* thread = Thread::current();
assert(thread->is_Java_thread(), "Must be JavaThread");
JavaThread *jt = (JavaThread *)thread;
...
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);
}
...
if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
return;
}
int status ;
if (_counter > 0) { // no wait needed
_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;
}
assert(_cur_index == -1, "invariant");
if (time == 0) {
_cur_index = REL_INDEX; // arbitrary choice when not timed
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());
}
}
...
_counter = 0 ;
status = pthread_mutex_unlock(_mutex) ;
OrderAccess::fence();
// If externally suspended while waiting, re-suspend
if (jt->handle_special_suspend_equivalent_condition()) {
jt->java_suspend_self();
}
}
複製代碼
首先經過 Atomic::xchg(0, &_counter) 方法將 _counter 置 0,若是原來的 _counter > 0,說明原來的許但是可用的,直接返回;
若是當前線程的處於中斷狀態,直接返回;
Thread::is_interrupted(thread, false) 只會判斷線程的中斷狀態,不會重置其中斷狀態;Thread.interrupted() 調用的是 Thread::is_interrupted(thread, true),判斷線程的中斷狀態同時將其重置。
關於 pthread_cond_wait 能夠看一下這篇文章: pthread_cond_wait 詳解
感受和 Object.wait、Object.notify、Object.notifyAll 的機制很相似。
unsafe.cpp#Unsafe_Unpark
:openjdk/hotspot/src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(void, Unsafe_Unpark(JNIEnv *env, jobject unsafe, jobject jthread))
UnsafeWrapper("Unsafe_Unpark");
Parker* p = NULL;
if (jthread != NULL) {
oop java_thread = JNIHandles::resolve_non_null(jthread);
if (java_thread != NULL) {
jlong lp = java_lang_Thread::park_event(java_thread);
if (lp != 0) {
// This cast is OK even though the jlong might have been read
// non-atomically on 32bit systems, since there, one word will
// always be zero anyway and the value set is always the same
p = (Parker*)addr_from_java(lp);
} else {
// Grab lock if apparently null or using older version of library
MutexLocker mu(Threads_lock);
java_thread = JNIHandles::resolve_non_null(jthread);
if (java_thread != NULL) {
JavaThread* thr = java_lang_Thread::thread(java_thread);
if (thr != NULL) {
p = thr->parker();
if (p != NULL) { // Bind to Java thread for next time.
java_lang_Thread::set_park_event(java_thread, addr_to_java(p));
}
}
}
}
}
}
if (p != NULL) {
#ifndef USDT2
HS_DTRACE_PROBE1(hotspot, thread__unpark, p);
#else /* USDT2 */
HOTSPOT_THREAD_UNPARK(
(uintptr_t) p);
#endif /* USDT2 */
p->unpark();
}
UNSAFE_END
複製代碼
前面一大段代碼是在給 Parker* p 賦值,最終調用的是 p 的 unpark 方法:p->unpark()。
os_linux.cpp#Parker::unpark
:openjdk/hotspot/src/os/linux/vm/os_linux.cpp
void Parker::unpark() {
int s, status ;
status = pthread_mutex_lock(_mutex);
assert (status == 0, "invariant") ;
s = _counter;
_counter = 1;
if (s < 1) {
// thread might be parked
if (_cur_index != -1) {
// thread is definitely parked
if (WorkAroundNPTLTimedWaitHang) {
status = pthread_cond_signal (&_cond[_cur_index]);
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[_cur_index]);
assert (status == 0, "invariant");
}
} else {
pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
}
} else {
pthread_mutex_unlock(_mutex);
assert (status == 0, "invariant") ;
}
}
複製代碼
unpark 方法其實很簡單,首先經過 pthread_mutex_lock 獲取鎖,而後將 _counter 置爲 1,再判斷當前是否有線程被掛起,若是有,則經過 pthread_cond_signal 喚醒被掛起的線程,而後釋放鎖。
學習知識真是一環扣一環,學一個不會的知識點,很容易碰到新的不會的知識點,而後不斷的接觸新知識點,不斷地學習新的內容;當你不斷的學習,不斷的把新知識點吃透,慢慢的又會發現其實不少知識點的思想又是相通的,學起來反而沒那麼費勁了。
就比如看到 pthread_cond_wait 的機制時,就會聯想到 Object.wait,由於二者的實現有不少相像的地方,理解起來也就比較簡單。
最後留一個問題,你們能夠思考一下:LockSupport.park 和 Object.wait 二者有何區別?
參考資料: