讓面試官心服口服:Thread.sleep、synchronized、LockSupport.park的線程阻塞有何區(qū)別?
前言
在日常編碼的過(guò)程中,我們經(jīng)常會(huì)使用Thread.sleep、LockSupport.park()主動(dòng)阻塞線程,或者使用synchronized和Object.wait來(lái)阻塞線程保證并發(fā)安全。此時(shí)我們會(huì)發(fā)現(xiàn),對(duì)于Thread.sleep和Object.wait方法是會(huì)拋出InterruptedException,而LockSupport.park()和synchronized則不會(huì)。而當(dāng)我們調(diào)用Thread.interrupt方法時(shí),除了synchronized,其他線程阻塞的方式都會(huì)被喚醒。
于是本文就來(lái)探究一下Thread.sleep、LockSupport.park()、synchronized和Object.wait的線程阻塞的原理以及InterruptedException的本質(zhì)
本文主要分為以下幾個(gè)部分
1.Thread.sleep的原理
2.LockSupport.park()的原理
3.synchronized線程阻塞的原理
4.ParkEvent和parker對(duì)象的原理
5.Thread.interrupt的原理
6.對(duì)于synchronized打斷原理的擴(kuò)展
1.Thread.sleep的原理
Thread.java
首先還是從java入手,查看sleep方法,可以發(fā)現(xiàn)它直接就是一個(gè)native方法:
public static native void sleep(long millis) throws InterruptedException;
為了查看native方法的具體邏輯,我們就需要下載openjdk和hotspot的源碼了,下載地址:http://hg.openjdk.java.net/jdk8
查看Thread.c:jdk源碼目錄src/java.base/share/native/libjava
可以看到對(duì)應(yīng)的jvm方法是JVM_Sleep:
static JNINativeMethod methods[] = {
...
{"sleep", "(J)V", (void *)&JVM_Sleep},
...
};
查看jvm.cpp,hotspot目錄src/share/vm/prims
找到JVM_Sleep方法,我們關(guān)注其重點(diǎn)邏輯:
方法的邏輯中,首先會(huì)做2個(gè)校驗(yàn),分別是睡眠時(shí)間和線程的打斷標(biāo)記。其實(shí)這2個(gè)數(shù)據(jù)的校驗(yàn)都是可以放到j(luò)ava層,不過(guò)jvm的設(shè)計(jì)者將其放到了jvm的邏輯中去判斷。
如果睡眠的時(shí)間為0,那么會(huì)調(diào)用系統(tǒng)級(jí)別的睡眠方法os::sleep(),睡眠時(shí)間為最小時(shí)間間隔。在睡眠之前會(huì)保存線程當(dāng)前的狀態(tài),并將其設(shè)置為SLEEPING。在睡眠結(jié)束之后恢復(fù)線程狀態(tài)。
接著就是sleep方法的重點(diǎn),如果睡眠時(shí)間不為0,同樣需要保存和恢復(fù)線程的狀態(tài),并調(diào)用系統(tǒng)級(jí)別的睡眠方法os::sleep()。當(dāng)然睡眠的時(shí)間會(huì)變成指定的毫秒數(shù)。
最重要的區(qū)別是,此時(shí)會(huì)判斷os::sleep()的返回值,如果是打斷狀態(tài),那么就會(huì)拋出一個(gè)InterruptException!這里其實(shí)就是InterruptException產(chǎn)生的源頭
JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis))
JVMWrapper("JVM_Sleep");
//如果睡眠的時(shí)間小于0,則拋出異常。這里數(shù)據(jù)的校驗(yàn)在jvm層邏輯中校驗(yàn)
if (millis < 0) {
THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
}
//如果線程已經(jīng)被打斷了,那么也拋出異常
if (Thread::is_interrupted (THREAD, true) && !HAS_PENDING_EXCEPTION) {
THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
}
...
//這里允許睡眠時(shí)間為0
if (millis == 0) {
...{
//獲取并保存線程的舊狀態(tài)
ThreadState old_state = thread->osthread()->get_state();
//將線程的狀態(tài)設(shè)置為SLEEPING
thread->osthread()->set_state(SLEEPING);
//調(diào)用系統(tǒng)級(jí)別的sleep方法,此時(shí)只會(huì)睡眠最小時(shí)間間隔
os::sleep(thread, MinSleepInterval, false);
//恢復(fù)線程的狀態(tài)
thread->osthread()->set_state(old_state);
}
} else {
//獲取并保存線程的舊狀態(tài)
ThreadState old_state = thread->osthread()->get_state();
//將線程的狀態(tài)設(shè)置為SLEEPING
thread->osthread()->set_state(SLEEPING);
//睡眠指定的毫秒數(shù),并判斷返回值
if (os::sleep(thread, millis, true) == OS_INTRPT) {
...
//拋出InterruptedException異常
THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted");
}
//恢復(fù)線程的狀態(tài)
thread->osthread()->set_state(old_state);
}
JVM_END
查看os_posix.cpp,hotspot目錄src/os/posix/vm
我們接著查看os::sleep()方法:
首先獲取線程的SleepEvent對(duì)象,這個(gè)是線程睡眠的關(guān)鍵
根據(jù)是否允許打斷分為2個(gè)大分支,其中邏輯大部分是相同的,區(qū)別在于允許打斷的分支中會(huì)在循環(huán)中額外判斷打斷標(biāo)記,如果打斷標(biāo)記為true,則返回打斷狀態(tài),并在外層方法中拋出InterruptedException
最終線程睡眠是調(diào)用SleepEvent對(duì)象的park方法完成的,該對(duì)象內(nèi)部的原理后面統(tǒng)一說(shuō)
int os::sleep(Thread* thread, jlong millis, bool interruptible) {
//獲取thread中的_SleepEvent對(duì)象
ParkEvent * const slp = thread->_SleepEvent ;
...
//如果是允許被打斷
if (interruptible) {
//記錄下當(dāng)前時(shí)間戳,這是時(shí)間比較的基準(zhǔn)
jlong prevtime = javaTimeNanos();
for (;;) {
//檢查打斷標(biāo)記,如果打斷標(biāo)記為ture,則直接返回
if (os::is_interrupted(thread, true)) {
return OS_INTRPT;
}
//線程被喚醒后的當(dāng)前時(shí)間戳
jlong newtime = javaTimeNanos();
//睡眠毫秒數(shù)減去當(dāng)前已經(jīng)經(jīng)過(guò)的毫秒數(shù)
millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;
//如果小于0,那么說(shuō)明已經(jīng)睡眠了足夠多的時(shí)間,直接返回
if (millis <= 0) {
return OS_OK;
}
//更新基準(zhǔn)時(shí)間
prevtime = newtime;
//調(diào)用_SleepEvent對(duì)象的park方法,阻塞線程
slp->park(millis);
}
} else {
//如果不能打斷,除了不再返回OS_INTRPT以外,邏輯是完全相同的
for (;;) {
...
slp->park(millis);
...
}
return OS_OK ;
}
}
所以Thread.sleep的在jvm層面上是調(diào)用thread中SleepEvent對(duì)象的park()方法實(shí)現(xiàn)阻塞線程,在此過(guò)程中會(huì)通過(guò)判斷時(shí)間戳來(lái)決定線程的睡眠時(shí)間是否達(dá)到了指定的毫秒。
而InterruptedException的本質(zhì)是一個(gè)jvm級(jí)別對(duì)打斷標(biāo)記的判斷,并且jvm也提供了不可打斷的sleep邏輯。
2.LockSupport.park()的原理
除了我們經(jīng)常使用的Thread.sleep,在jdk中還有很多時(shí)候需要阻塞線程時(shí)使用的是LockSupport.park()方法(例如ReentrantLock),接下去我們同樣需要看下LockSupport.park()的底層實(shí)現(xiàn)
LockSupport.java
從java代碼入手,查看LockSupport.park()方法,可以看到它直接調(diào)用了Usafe類中的park方法:
public static void park() {
UNSAFE.park(false, 0L);
}
Unsafe.java
查看Unsafe.park,可以看到是一個(gè)native方法
public native void park(boolean var1, long var2);
查看unsafe.cpp,hotspot目錄src/share/vm/prims
找到park方法,這個(gè)方法就比sleep簡(jiǎn)單粗暴多了,直接調(diào)用thread中的parker對(duì)象的park()方法阻塞線程
UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time)) {
...
//簡(jiǎn)單粗暴,直接調(diào)用thread中的parker對(duì)象的park方法阻塞線程
thread->parker()->park(isAbsolute != 0, time);
...
} UNSAFE_END
所以LockSupport.park方法是不會(huì)拋出InterruptedException異常的。當(dāng)一個(gè)線程調(diào)用LockSupport.park阻塞后,如果被喚醒,那么就直接執(zhí)行之后的邏輯。而對(duì)于打斷的響應(yīng)則需要使用該方法的用戶在Java級(jí)別的代碼上通過(guò)調(diào)用Thread.interrupted()判斷打斷標(biāo)記自行處理。
相比而言Thread.sleep則設(shè)計(jì)更為復(fù)雜,除了在jvm級(jí)別上對(duì)打斷作出響應(yīng),更提供了不可被打斷的邏輯,保證調(diào)用該方法的線程一定可以阻塞指定的時(shí)間,而這個(gè)功能是LockSupport.park所做不到的。
3.synchronized線程阻塞的原理
再看一下synchronized在線程阻塞上的原理。synchronized本身其實(shí)都可寫幾篇文章來(lái)探討,不過(guò)本文僅關(guān)注于其線程阻塞部分的邏輯。
synchronized的阻塞包括2部分:
1.調(diào)用synchronized(obj)時(shí),如果沒(méi)有搶到鎖,那么會(huì)進(jìn)入隊(duì)列等待,并阻塞線程。
2.獲取到鎖之后,調(diào)用obj.wait()方法進(jìn)行等待,此時(shí)也會(huì)阻塞線程。
先來(lái)看情況一。因?yàn)檫@種情況并非是調(diào)用類中的某個(gè)方法,而是一個(gè)關(guān)鍵字,因此我們是無(wú)法從某個(gè)類文件入手。那么我們就需要直接查看字節(jié)碼了。
首先創(chuàng)建一個(gè)簡(jiǎn)單的java類
public class Synchronized{
public void test(){
synchronized(this){
}
}
}
編譯成.class文件后,再查看其字節(jié)碼
javac Synchronized.java
javap -v Synchronized.class
synchronized關(guān)鍵字在字節(jié)碼上體現(xiàn)為monitorenter和monitorexit指令。
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
...
3: monitorenter
4: aload_1
5: monitorexit
...
查看bytecodeInterpreter.cpp,hotspot目錄/src/share/vm/interpreter
該文件中的方法都是用來(lái)解析各種字節(jié)碼命令的。接著我們找到monitorenter方法:
這個(gè)方法就是synchronized關(guān)鍵字的具體加鎖邏輯,十分復(fù)雜,這里只是展示方法的入口在哪里。
CASE(_monitorenter): {
...
}
查看objectMonitor.cpp,hotspot目錄/src/share/vm/runtime
最終synchronized的線程阻塞邏輯是由objectMonitor對(duì)象負(fù)責(zé)的,所以我們直接查看該對(duì)象的相應(yīng)方法。找到enter方法:
跳過(guò)其中大部分邏輯,我們看到EnterI方法,正是在該方法中阻塞線程的。
void ObjectMonitor::enter(TRAPS) {
...
//阻塞線程
EnterI(THREAD);
...
}
查看EnterI方法
這個(gè)方法會(huì)在一個(gè)死循環(huán)中嘗試獲取鎖,如果獲取失敗則調(diào)用當(dāng)前線程的ParkEvent的park()方法阻塞線程,否則就退出循環(huán)
當(dāng)然特別注意的是,這個(gè)方法是在一個(gè)死循環(huán)中調(diào)用的,因此在java級(jí)別來(lái)看,synchronized是不可打斷的,線程會(huì)一直阻塞直到它獲取到鎖為止。
void ObjectMonitor::EnterI(TRAPS) {
//獲取當(dāng)前線程對(duì)象
Thread * const Self = THREAD;
...
for (;;) {
//嘗試獲取鎖
if (TryLock(Self) > 0) break;
...
//調(diào)用ParkEvent的park()方法阻塞線程
if (_Responsible == Self || (SyncFlags & 1)) {
Self->_ParkEvent->park((jlong) recheckInterval);
} else {
Self->_ParkEvent->park();
}
...
}
...
}
接著來(lái)看情況二:
查看objectMonitor.cpp,hotspot目錄/src/share/vm/runtime
最終Object.wait()的線程阻塞邏輯也是由objectMonitor對(duì)象負(fù)責(zé)的,所以我們直接查看該對(duì)象的相應(yīng)方法。找到wait方法:
可以看到wait()方法中對(duì)線程的打斷作出了響應(yīng),并且會(huì)拋出InterruptedException,這也正是java級(jí)別的Object.wait()方法會(huì)拋出該異常的原因
線程阻塞和synchronized一樣,是由線程的ParkEvent對(duì)象的park()方法完成的
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
//獲取當(dāng)前線程對(duì)象
Thread * const Self = THREAD;
//檢查是否可以打斷
if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) {
...
//拋出InterruptedException
THROW(vmSymbols::java_lang_InterruptedException());
}
if (interruptible && (Thread::is_interrupted(THREAD, false) || HAS_PENDING_EXCEPTION)) {
//如果線程被打斷了,那就什么都不做
} else if (node._notified == 0) {
//調(diào)用ParkEvent的park()方法阻塞線程
if (millis <= 0) {
Self->_ParkEvent->park();
} else {
ret = Self->_ParkEvent->park(millis);
}
}
}
所以對(duì)于synchronized和Object.wait來(lái)說(shuō),最終都是調(diào)用thread中ParkEvent對(duì)象的park()方法實(shí)現(xiàn)線程阻塞的
而在java層面上synchronized本身是不響應(yīng)線程打斷的,但是Object.wait()方法卻是會(huì)響應(yīng)打斷的,區(qū)別正是在于jvm級(jí)別的邏輯處理上有所不同。
4.ParkEvent和parker對(duì)象的原理
Thread.sleep、synchronized和Object.wait底層分別是利用線程SleepEvent和ParkEvent對(duì)象的park方法實(shí)現(xiàn)線程阻塞的。因?yàn)檫@2個(gè)對(duì)象實(shí)際是一個(gè)類型的,因此我們就一起來(lái)看一下其park方法究竟做了什么
查看thread.cpp,hotspot目錄src/share/vm/runtime
找到SleepEvent和ParkEvent的定義,從后面的注釋就可以發(fā)現(xiàn),ParkEvent就是供synchronized()使用的,而SleepEvent則是供Thread.sleep使用的:
ParkEvent * _ParkEvent; // for synchronized()
ParkEvent * _SleepEvent; // for Thread.sleep
查看park.hpp,hotspot目錄src/share/vm/runtime
在頭文件中能找到ParkEvent類的定義,繼承自os::PlatformEvent:
class ParkEvent : public os::PlatformEvent {
...
}
查看os_linux.hpp,hotspot目錄src/os/linux/vm
以linux系統(tǒng)為例,在頭文件中可以看到PlatformEvent的具體定義:
我們關(guān)注的重點(diǎn)首先是2個(gè)private的對(duì)象,一個(gè)pthread_mutex_t,表示操作系統(tǒng)級(jí)別的信號(hào)量,一個(gè)pthread_cond_t,表示操作系統(tǒng)級(jí)別的條件變量
其次是定義了3個(gè)方法,park()、unpark()、park(jlong millis),控制線程的阻塞和繼續(xù)執(zhí)行
class PlatformEvent : public CHeapObj<mtInternal> {
private:
...
pthread_mutex_t _mutex[1];
pthread_cond_t _cond[1];
...
void park();
void unpark();
int park(jlong millis); // relative timed-wait only
...
};
查看os_linux.cpp,hotspot目錄src/os/linux/vm
接著我們就需要去看park方法的具體實(shí)現(xiàn),這里我們主要關(guān)注3個(gè)系統(tǒng)底層方法的調(diào)用
pthread_mutex_lock(_mutex):鎖住信號(hào)量
status = pthread_cond_wait(_cond, _mutex):釋放信號(hào)量,并在條件變量上等待
status = pthread_mutex_unlock(_mutex):釋放信號(hào)量
void os::PlatformEvent::park() {
...
//鎖住信號(hào)量
int status = pthread_mutex_lock(_mutex);
while (_Event < 0) {
//釋放信號(hào)量,并在條件變量上等待
status = pthread_cond_wait(_cond, _mutex);
}
//釋放信號(hào)量
status = pthread_mutex_unlock(_mutex);
}
可以看到ParkEvent的park()方法底層最終是調(diào)用系統(tǒng)函數(shù)pthread_cond_wait完成線程阻塞的操作。
而線程的parker對(duì)象的park()方法本質(zhì)和ParkEvent是完全一致的,最終也是調(diào)用系統(tǒng)函數(shù)pthread_cond_wait完成線程阻塞的操作,區(qū)別只是在于多了一個(gè)絕對(duì)時(shí)間的判斷:
查看os_linux.cpp,hotspot目錄src/os/linux/vm
void Parker::park(bool isAbsolute, jlong time) {
...
if (time == 0) {
//這里是直接長(zhǎng)時(shí)間等待
_cur_index = REL_INDEX;
status = pthread_cond_wait(&_cond[_cur_index], _mutex);
} else {
//這里會(huì)根據(jù)時(shí)間是否是絕對(duì)時(shí)間,分別等待在不同的條件上
_cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
status = pthread_cond_timedwait(&_cond[_cur_index], _mutex, &absTime);
}
...
}
5.Thread.interrupt的原理
上面看了3中線程阻塞的原理,那么接著自然是需要看一下線程打斷在jvm層面上到底做了什么。我們跳過(guò)代碼搜尋的過(guò)程,直接看最后一步的源碼
查看os_posix.cpp,hotspot目錄src/os/posix/vm
找到interrupt方法,這個(gè)方法正是打斷的重點(diǎn),其中一共做了2件事情:
1.將打斷標(biāo)記置為true
2.分別調(diào)用thread中的ParkEvent、SleepEvent和Parker對(duì)象的unpark()方法
void os::interrupt(Thread* thread) {
...
//獲得c++線程對(duì)應(yīng)的系統(tǒng)線程
OSThread* osthread = thread->osthread();
//如果系統(tǒng)線程的打斷標(biāo)記是false,意味著還未被打斷
if (!osthread->interrupted()) {
//將系統(tǒng)線程的打斷標(biāo)記設(shè)為true
osthread->set_interrupted(true);
//這個(gè)涉及到內(nèi)存屏障,本文不展開(kāi)
OrderAccess::fence();
//這里獲取一個(gè)_SleepEvent,并調(diào)用其unpark()方法
ParkEvent * const slp = thread->_SleepEvent ;
if (slp != NULL) slp->unpark() ;
}
//這里依據(jù)JSR166標(biāo)準(zhǔn),即使打斷標(biāo)記為true,依然要調(diào)用下面的2個(gè)unpark
if (thread->is_Java_thread())
//如果是一個(gè)java線程,這里獲取一個(gè)parker對(duì)象,并調(diào)用其unpark()方法
((JavaThread*)thread)->parker()->unpark();
ParkEvent * ev = thread->_ParkEvent ;
//這里獲取一個(gè)_ParkEvent,并調(diào)用其unpark()方法
if (ev != NULL) ev->unpark() ;
}
通過(guò)對(duì)3個(gè)park對(duì)象park()方法的了解,在unpark中必然是調(diào)用系統(tǒng)級(jí)別的signal方法:
void os::PlatformEvent::unpark() {
...
if (AnyWaiters != 0) {
//喚醒條件變量
status = pthread_cond_signal(_cond);
}
...
}
所以對(duì)于Thread.interrupt來(lái)說(shuō),它最重要的事情其實(shí)是調(diào)用3個(gè)unpark()方法對(duì)象喚醒線程,而我們老生常談的修改打斷標(biāo)記,反倒是沒(méi)那么重要。是否響應(yīng)該標(biāo)記、是在jvm層上響應(yīng)還是在java層上響應(yīng)等等邏輯,都取決于實(shí)際需要。
6.對(duì)于synchronized的擴(kuò)展
在synchronized的原理部分,我們看到線程的阻塞是在一個(gè)死循環(huán)中執(zhí)行的,因此在java級(jí)別上看來(lái)是不可打斷的。
如果了解synchronized的原理(不了解也沒(méi)關(guān)系,一會(huì)兒會(huì)有實(shí)際示例),可以知道當(dāng)線程沒(méi)有搶到鎖時(shí)會(huì)進(jìn)入一個(gè)隊(duì)列并阻塞,而線程的正常喚醒順序會(huì)按照入隊(duì)列的順序依次進(jìn)行。
然而,如果我們仔細(xì)看jvm的邏輯,可以發(fā)現(xiàn)在循環(huán)中,每當(dāng)線程被喚醒后都會(huì)去調(diào)用TryLock方法嘗試獲取鎖,那么結(jié)合我們對(duì)Thread.interrupt方法的了解
我們就可以大膽推測(cè),雖然在java級(jí)別上synchronized不可打斷,但是如果我們不斷地調(diào)用Thread.interrupt方法就能使得線程直接插隊(duì)獲取鎖,而不必按照入隊(duì)列的順序了!
接下來(lái)我們來(lái)看示例
1.synchronized的順序性
這里我們先讓一個(gè)線程獲取到鎖,之后啟動(dòng)3個(gè)線程等待在鎖上。
@Test
public void synchronizedTest() throws InterruptedException {
int size = 3;
Object lock = new Object();
//讓第一個(gè)線程獲取鎖后阻塞1秒鐘
new Thread(() -> {
synchronized (lock) {
System.out.println("Thread Lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Lock Over");
}
}).start();
TimeUnit.MILLISECONDS.sleep(10);
//啟動(dòng)3個(gè)線程,并等待第一個(gè)線程釋放鎖,每個(gè)線程啟動(dòng)間隔10毫秒,保證入隊(duì)列的順序性
int count = 1;
for (int i = 0; i < size; i++) {
int m = count++;
TimeUnit.MILLISECONDS.sleep(10);
new Thread(() -> {
synchronized (lock) {
System.out.println(Thread.currentThread().getName());
}
}, "thread--" + m).start();
}
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}
最終輸出結(jié)果,可以看到synchronized的隊(duì)列遵循先入后出的原則
Thread Lock
Lock Over
thread--3
thread--2
thread--1
2.線程打斷對(duì)隊(duì)列順序的影響
在啟動(dòng)3個(gè)線程入隊(duì)列之前,我們先啟動(dòng)一個(gè)單獨(dú)的線程。并且在主線程的最后,我們?cè)谝粋€(gè)死循環(huán)中不斷調(diào)用該單獨(dú)線程的interrupt方法。
@Test
public void synchronizedTest() throws InterruptedException {
int size = 3;
Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
System.out.println("Thread Lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Lock Over");
}
}).start();
TimeUnit.MILLISECONDS.sleep(10);
//啟動(dòng)一個(gè)單獨(dú)的線程,用來(lái)測(cè)試synchronized的打斷
Thread interruptThread = new Thread(() -> {
synchronized (lock) {
System.out.println("interruptThread");
}
});
interruptThread.start();
TimeUnit.MILLISECONDS.sleep(10);
int count = 1;
for (int i = 0; i < size; i++) {
int m = count++;
TimeUnit.MILLISECONDS.sleep(10);
new Thread(() -> {
synchronized (lock) {
System.out.println(Thread.currentThread().getName());
}
}, "thread--" + m).start();
}
//在主線程中不斷打斷單獨(dú)線程
for(;;){
interruptThread.interrupt();
}
}
輸出如下,按照先入后出的原則,這個(gè)單獨(dú)的線程應(yīng)該是最后一個(gè)獲取到鎖的。然而在主線程不斷地打斷下,它成功地完成了插隊(duì)!而其他沒(méi)有被打斷的線程依然按照約定的順序依次喚醒。有興趣的同學(xué)可以嘗試去掉最后的打斷,再運(yùn)行一次。
Thread Lock
Lock Over
interruptThread
thread--3
thread--2
thread--1
最后總結(jié)一下Thread.sleep、LockSupport.park和synchronized線程阻塞方式的區(qū)別,這里我分幾個(gè)層次來(lái)總結(jié)
1.系統(tǒng)級(jí)別:這3種方式?jīng)]有區(qū)別,最終都是調(diào)用系統(tǒng)的pthread_cond_wait方法
2.c++線程級(jí)別:Thread.sleep使用的是線程的SleepEvent對(duì)象,LockSupport.park使用的是線程的Parker對(duì)象,synchronized和Object.wait使用的是線程的ParkEvent對(duì)象
3.java級(jí)別:Thread.sleep可打斷并拋出異常;LockSupport.park可打斷,且不會(huì)拋出異常;synchronized不可打斷;Object.wait可打斷并拋出異常
4.InterruptedException其實(shí)僅僅是jvm邏輯上對(duì)打斷標(biāo)記的判斷而已
5.Thread.interrupt的本質(zhì)在于修改打斷標(biāo)記,并調(diào)用3個(gè)unpark()方法喚醒線程
4.更概括來(lái)說(shuō),無(wú)論是哪種線程阻塞的方式,在系統(tǒng)級(jí)別和c++線程級(jí)別來(lái)說(shuō)都是可打斷的。而jvm通過(guò)代碼邏輯使得3種線程阻塞的方式在java級(jí)別上面對(duì)同一個(gè)打斷方法時(shí)會(huì)有不同的表現(xiàn)形式