讓面試官心服口服:Thread.sleep、synchronized、LockSupport.park的線程阻塞有何區(qū)別?

讓面試官心服口服: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)為monitorentermonitorexit指令。

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)前線程的ParkEventpark()方法阻塞線程,否則就退出循環(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ì)于synchronizedObject.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底層分別是利用線程SleepEventParkEvent對(duì)象的park方法實(shí)現(xiàn)線程阻塞的。因?yàn)檫@2個(gè)對(duì)象實(shí)際是一個(gè)類型的,因此我們就一起來(lái)看一下其park方法究竟做了什么

查看thread.cpp,hotspot目錄src/share/vm/runtime

找到SleepEventParkEvent的定義,從后面的注釋就可以發(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ì)象,synchronizedObject.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)形式

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容