Java線程源碼解析之interrupt

概述

Thread提供了interrupt方法茶袒,中斷線程的執(zhí)行:

  • 如果線程堵塞在object.wait羽戒、Thread.join和Thread.sleep勺远,將會(huì)拋出InterruptedException,同時(shí)清除線程的中斷狀態(tài);
  • 如果線程堵塞在java.nio.channels.InterruptibleChannel的IO上首尼,Channel將會(huì)被關(guān)閉蝶锋,線程被置為中斷狀態(tài)陆爽,并拋出java.nio.channels.ClosedByInterruptException;
  • 如果線程堵塞在java.nio.channels.Selector上扳缕,線程被置為中斷狀態(tài)慌闭,select方法會(huì)馬上返回恶守,類似調(diào)用wakeup的效果;
public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

源碼實(shí)現(xiàn)

之前在分析Thread.start的時(shí)候曾經(jīng)提到贡必,JavaThread有三個(gè)成員變量:

//用于synchronized同步塊和Object.wait() 
ParkEvent * _ParkEvent ; 
//用于Thread.sleep() 
ParkEvent * _SleepEvent ; 
//用于unsafe.park()/unpark(),供java.util.concurrent.locks.LockSupport調(diào)用兔港, 
//因此它支持了java.util.concurrent的各種鎖、條件變量等線程同步操作,是concurrent的實(shí)現(xiàn)基礎(chǔ) 
Parker* _parker;

初步猜測(cè)interrupt實(shí)現(xiàn)應(yīng)該與此有關(guān)系仔拟;
interrupt方法的源碼也在jvm.cpp文件:

JVM_ENTRY(void, JVM_Interrupt(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_Interrupt");

  // Ensure that the C++ Thread and OSThread structures aren't freed before we operate
  oop java_thread = JNIHandles::resolve_non_null(jthread);
  MutexLockerEx ml(thread->threadObj() == java_thread ? NULL : Threads_lock);
  // We need to re-resolve the java_thread, since a GC might have happened during the
  // acquire of the lock
  JavaThread* thr = java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread));
  if (thr != NULL) {
    Thread::interrupt(thr);
  }
JVM_END

JVM_Interrupt對(duì)參賽進(jìn)行了校驗(yàn)衫樊,然后直接調(diào)用Thread::interrupt:

void Thread::interrupt(Thread* thread) {
  trace("interrupt", thread);
  debug_only(check_for_dangling_thread_pointer(thread);)
  os::interrupt(thread);
}

Thread::interrupt調(diào)用os::interrupt方法實(shí)現(xiàn),os::interrupt方法定義在os_linux.cpp:

void os::interrupt(Thread* thread) {
  assert(Thread::current() == thread || Threads_lock->owned_by_self(),
    "possibility of dangling Thread pointer");

  //獲取系統(tǒng)native線程對(duì)象
  OSThread* osthread = thread->osthread();

  if (!osthread->interrupted()) {
    osthread->set_interrupted(true);
   //內(nèi)存屏障,使osthread的interrupted狀態(tài)對(duì)其它線程立即可見(jiàn)
    OrderAccess::fence();
    //前文說(shuō)過(guò)利花,_SleepEvent用于Thread.sleep,線程調(diào)用了sleep方法科侈,則通過(guò)unpark喚醒
    ParkEvent * const slp = thread->_SleepEvent ;
    if (slp != NULL) slp->unpark() ;
  }

  //_parker用于concurrent相關(guān)的鎖,此處同樣通過(guò)unpark喚醒
  if (thread->is_Java_thread())
    ((JavaThread*)thread)->parker()->unpark();
  //synchronized同步塊和Object.wait() 喚醒
  ParkEvent * ev = thread->_ParkEvent ;
  if (ev != NULL) ev->unpark() ;

}

由此可見(jiàn)炒事,interrupt其實(shí)就是通過(guò)ParkEvent的unpark方法喚醒對(duì)象臀栈;另外要注意:

  • object.wait、Thread.sleep和Thread.join會(huì)拋出InterruptedException并清除中斷狀態(tài)挠乳;
  • Lock.lock()方法不會(huì)響應(yīng)中斷权薯,Lock.lockInterruptibly()方法則會(huì)響應(yīng)中斷并拋出異常,區(qū)別在于park()等待被喚醒時(shí)lock會(huì)繼續(xù)執(zhí)行park()來(lái)等待鎖睡扬,而 lockInterruptibly會(huì)拋出異常盟蚣;
  • synchronized被喚醒后會(huì)嘗試獲取鎖,失敗則會(huì)通過(guò)循環(huán)繼續(xù)park()等待卖怜,因此實(shí)際上是不會(huì)被interrupt()中斷的;
  • 一般情況下屎开,拋出異常時(shí),會(huì)清空Thread的interrupt狀態(tài)马靠,在編程時(shí)需要注意奄抽;

網(wǎng)絡(luò)相關(guān)的中斷

之前的interrupt方法有這么一段:

private volatile Interruptible blocker;
private final Object blockerLock = new Object();
synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }

其中blocker是Thread的成員變量,Thread提供了blockedOn方法可以設(shè)置blocker:

 void blockedOn(Interruptible b) {
        synchronized (blockerLock) {
            blocker = b;
        }
    }

如果一個(gè)nio通道實(shí)現(xiàn)了InterruptibleChannel接口,就可以響應(yīng)interrupt()中斷甩鳄,其原理就在InterruptibleChannel接口的抽象實(shí)現(xiàn)類AbstractInterruptibleChannel的方法begin()中:

 protected final void begin() {
        if (interruptor == null) {
            interruptor = new Interruptible() {
                    public void interrupt(Thread target) {
                        synchronized (closeLock) {
                            if (!open)
                                return;
                            open = false;
                            interrupted = target;
                            try {
                                AbstractInterruptibleChannel.this.implCloseChannel();
                            } catch (IOException x) { }
                        }
                    }};
        }
        blockedOn(interruptor);//設(shè)置當(dāng)前線程的blocker為interruptor
        Thread me = Thread.currentThread();
        if (me.isInterrupted())
            interruptor.interrupt(me);
    }

 protected final void end(boolean completed)
        throws AsynchronousCloseException
    {
        blockedOn(null);//設(shè)置當(dāng)前線程的blocker為null
        Thread interrupted = this.interrupted;
       //如果發(fā)生中斷逞度,Thread.interrupt方法會(huì)調(diào)用Interruptible的interrupt方法,
      //設(shè)置this.interrupted為當(dāng)前線程
        if (interrupted != null && interrupted == Thread.currentThread()) {
            interrupted = null;
            throw new ClosedByInterruptException();
        }
        if (!completed && !open)
            throw new AsynchronousCloseException();
    }
//Class java.nio.channels.Channels.WritableByteChannelImpl
     public int write(ByteBuffer src) throws IOException {
        ......    
        try {
            begin();
            out.write(buf, 0, bytesToWrite);
        finally {
            end(bytesToWrite > 0);
        }
        ......
    }

//Class java.nio.channels.Channels.ReadableByteChannelImpl
    public int read(ByteBuffer dst) throws IOException {
        ......    
        try {
            begin();
            bytesRead = in.read(buf, 0, bytesToRead);
        finally {
            end(bytesRead > 0);
        }
        ......
    }

以上述代碼為例娩贷,nio通道的ReadableByteChannel每次執(zhí)行阻塞方法read()前第晰,都會(huì)執(zhí)行begin(),把Interruptible回調(diào)接口注冊(cè)到當(dāng)前線程上彬祖。當(dāng)線程中斷時(shí)茁瘦,Thread.interrupt()觸發(fā)回調(diào)接口Interruptible關(guān)閉io通道,導(dǎo)致read方法返回,最后在finally塊中執(zhí)行end()方法檢查中斷標(biāo)記储笑,拋出ClosedByInterruptException;

Selector的實(shí)現(xiàn)類似:

//java.nio.channels.spi.AbstractSelector
  protected final void begin() {
        if (interruptor == null) {
            interruptor = new Interruptible() {
                    public void interrupt(Thread ignore) {
                        AbstractSelector.this.wakeup();
                    }};
        }
        AbstractInterruptibleChannel.blockedOn(interruptor);
        Thread me = Thread.currentThread();
        if (me.isInterrupted())
            interruptor.interrupt(me);
    }
protected final void end() {
    AbstractInterruptibleChannel.blockedOn(null);
}
//sun.nio.ch.class EPollSelectorImpl
protected int doSelect(long timeout) throws IOException {
        ......
        try {
            begin();
            pollWrapper.poll(timeout);
        } finally {
            end();
        }
        ......
    }

可以看到當(dāng)發(fā)生中斷時(shí)會(huì)調(diào)用wakeup方法喚醒poll方法甜熔,但并不會(huì)拋出中斷異常;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末突倍,一起剝皮案震驚了整個(gè)濱河市腔稀,隨后出現(xiàn)的幾起案子盆昙,更是在濱河造成了極大的恐慌,老刑警劉巖焊虏,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淡喜,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡诵闭,警方通過(guò)查閱死者的電腦和手機(jī)炼团,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)疏尿,“玉大人瘟芝,你說(shuō)我怎么就攤上這事∪焖觯” “怎么了锌俱?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)敌呈。 經(jīng)常有香客問(wèn)我贸宏,道長(zhǎng),這世上最難降的妖魔是什么驱富? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任锚赤,我火速辦了婚禮,結(jié)果婚禮上褐鸥,老公的妹妹穿的比我還像新娘。我一直安慰自己赐稽,他們只是感情好叫榕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著姊舵,像睡著了一般晰绎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上括丁,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天荞下,我揣著相機(jī)與錄音,去河邊找鬼史飞。 笑死尖昏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的构资。 我是一名探鬼主播抽诉,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吐绵!你這毒婦竟也來(lái)了迹淌?” 一聲冷哼從身側(cè)響起河绽,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唉窃,沒(méi)想到半個(gè)月后耙饰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纹份,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年榔幸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矮嫉。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡削咆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蠢笋,到底是詐尸還是另有隱情拨齐,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布昨寞,位于F島的核電站瞻惋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏援岩。R本人自食惡果不足惜歼狼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望享怀。 院中可真熱鬧羽峰,春花似錦、人聲如沸添瓷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鳞贷。三九已至坯汤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間搀愧,已是汗流浹背惰聂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咱筛,地道東北人搓幌。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像眷蚓,于是被迫代替她去往敵國(guó)和親鼻种。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容