【併發編程】 --- Reentrantlock源碼解析5:再探不可中斷性 + 線程unpark後詭異的Thread.interrupted()判斷

源碼地址:https://github.com/nieandsun/concurrent-study.git


1 想要讀懂這篇文章必須要擁有的前置知識

【併發編程】 — synchronized/ReentrantLock兩大特性(可重入性和不可中斷性)介紹
【併發編程】— interrupt、interrupted和isInterrupted使用詳解
【併發編程】 — Reentrantlock源碼解析2:公平鎖加鎖過程超詳細解析
【併發編程】 — Reentrantlock源碼解析3:公平鎖釋放鎖過程超詳細解析


2 想寫這篇文章的原因

下面這幅圖是我在文章《【併發編程】 — Reentrantlock源碼解析3:公平鎖釋放鎖過程超詳細解析》中給出的一幅圖。
在這裏插入圖片描述
不知道大家對這幅圖中的代碼有沒有疑惑??? —> 說實話我有,而且困擾了我很久很久!!!
本篇文章將主要針對我困擾的問題進行討論,並給出我自認爲可以自圓其說的解釋!!!


3 困擾我很久的Reentrantlock源代碼1 — 貌似無用的變量failed

我們先來看一下1中紅色標號爲5的代碼。

我的疑問如下:
在這裏插入圖片描述
可能有人會說:

在try那塊代碼裏 failed = false;這句代碼之前有可能會拋出異常,如果拋了異常之後就會走finally代碼塊,那個時候failed還是初始值true ,那這個邏輯就有意義了。 —> 這句話說的一點沒錯,但是這是一種怎樣的情況呢???

假如你恰巧看過這篇文章《【併發編程】 — synchronized/ReentrantLock兩大特性(可重入性和不可中斷性)介紹》,那我覺得你應該是可以回答上來的:

使用Reentrantlock鎖,當某個線程正在被park時,使用stop方法中斷此線程,就正好是這種情況。


相信大家都知道使用stop方法中斷某個線程,會直接釋放掉本線程所持有的所有資源,舉個簡單的栗子來說,假如我們正在使用某個線程下載電影,如果該線程通過stop進行中斷,則原來下載的內容將全部丟失,因此可以簡單將其理解爲“暴力中斷”(自己發明的詞,見笑☺),只要調用了,就不可能恢復了 —》 應該正是基於這個原因,JDK官方將其@Deprecated了。

但是萬一有些業務確實就需要這種“暴力中斷”呢???

看過 《【併發編程】 — synchronized/ReentrantLock兩大特性(可重入性和不可中斷性)介紹》這篇文章的應該知道,

synchronized關鍵字的處理邏輯是,假如線程被park了,即使使用stop進行“暴力中斷”, 對不起, 沒法給你立即中斷 —》 但是當你有了搶鎖資格後,就立即給你中斷!!!

仔細想想這個邏輯 ,是不是一個挺操蛋的邏輯??? 我都採用暴力中斷了,你還不給我立即中斷,而當我有搶鎖資格了,我肯定這時候就大概率不想中斷了啊,你到立刻給我來個暴力中斷!!!

或許Doug Lea大神也覺得這個邏輯比較操蛋吧。。。所以人家在Reentrantlock中做了改進!!!

要很詳整的說出來具體的改進應該還要把cancelAcquire的源碼給搬出來,但是這裏我不打算搬出來了,所以只是簡單的,從比較宏觀的角度去闡述一下(當然私底下,我已經打過n遍斷點驗證過我的觀點了,有興趣的可以自己親自動手試試!!!)。


假設Node鏈表中有t2和t3在排隊等着搶鎖,其結構如下,這時候突然想用stop()方法“暴力中斷”t2線程。
在這裏插入圖片描述

在不看源碼的情況下,我覺得你仔細想想應該也能想出個一二來。 首先我們應該知道既然t2要被“暴力中斷”了,那這個線程說的直白點就直接相當於沒有了。。。 那該線程在Node鏈表裏肯定也就沒必要存在了吧; —》 反過來想,假若我不處理Node鏈表,在解鎖時發現Node鏈表裏竟然除了head外還有一個線程爲null的Node,你是不是感覺也很不可理解??? —> 想到這裏其實你就知道Doug Lea大神會幹什麼了 —》 沒錯,將被stop的線程從Node鏈表裏出隊。

以上圖而言Doug Lea大神的邏輯基本如下:

  • (1)既然stop了,線程會被暴力中斷,但是因爲有finally關鍵字,所以會進入到cancelAcquire()方法
  • (2)將線程t2對應的節點的waitStatus置爲1
  • (3)unpark線程t3 ,讓線程t3去自旋 —》 由文章《【併發編程】 — Reentrantlock源碼解析2:公平鎖加鎖過程超詳細解析》可知,由於線程t2所在Node的waitStatus此時爲1,因此會在t3自旋時,將其從Node鏈表中給踢出去 —》 由此便完成了被stop的線程t2所在節點的出隊。

4 困擾我很久的Reentrantlock源代碼2 — unpark後爲啥要來個Thread.interrupted();

這塊代碼真是困擾了很久很久很久!!!! - > 甚至都有時候都懷疑自己太鑽死牛角尖了!!!

昨天非常有幸,在B站看到了子路老師的講解,後又思考良久,甚至找子路老師聊了該問題,終於找到了一個可以自圓其說的原因!!!

在這裏插入圖片描述
如果看懂了我這篇文章《【併發編程】— interrupt、interrupted和isInterrupted使用詳解》再看上面這張圖,你腦子要TMD崩潰!!!

爲啥這樣說呢???

我們這裏直接看線程t被打斷的情況,他做了個啥啊,這是!!! —》 先判斷一下是否被打斷(這裏要注意一下interrupted和isInterrupted的區別,但是對於這裏不重要!!!) —》如果是 —》我再打斷一次

  • 而我想大家應該都知道使用interrupt做打斷,其實就相當於皇帝在某個妃子房間裏玩耍,突然某個太監在外面喊:萬歲,注意龍體,該去休息了。---》 但是皇帝到底停不停,全由它自己說了算!!!
  • 那TMD就讓人疑惑了,Doug Lea這是要幹啥啊,閒的蛋疼?在中間傳個話???
  • 如果它啥事不做,不也是這樣麼???—》 也就是說上面的流程不是和下面的流程一個樣麼???— 而且下面是不是效率還更高一些???

在這裏插入圖片描述
那Doug Lea爲啥要這樣搞呢???


研究了n天后,結合子路老師的解答,我給出的答案是爲了代碼格式的統一,以及某些鎖少寫幾行重複代碼!!!

首先來看一下用到parkAndCheckInterrupt() 這個方法的所有方法:

  • acquireQueued方法 — 獨佔鎖自旋主要邏輯
  • doAcquireInterruptibly方法 — 可打斷的獨佔鎖自旋主要邏輯
  • doAcquireShared方法 — 共享鎖自旋主要邏輯
  • doAcquireSharedInterruptibly方法— 可打斷的共享鎖自旋主要邏輯

這算不算格式上的統一??? —》 有興趣的可以留言交流!!!


其次來簡單看一下acquireQueueddoAcquireInterruptibly兩個方法的簡單比較:
在這裏插入圖片描述

這算不算在某些鎖少寫幾行重複代碼 —》 有興趣的可以留言交流!!!


5 小記

有時候感覺自己像個傻子!!! 總習慣於沿着一個方向不停的鑽鑽鑽!!!
上學時老師說我適合搞研究,能做事, 但又不適合,做的太多,研究的太深, 但是卻總不發文章。
於是思考了將近半年,下定決心要去工作,所幸手頭的工作都比較簡單 。

因此想快速搭建起自己的知識體系,但是發現自己還是會時不時的鑽鑽鑽!!!—》於是學的東西慢且少,而且越學越覺得不懂的越多!!! —》 所以¥¥¥ 。。。

唉!!!唉!!!唉!!!


end!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章