多線程-lock與lockInterruptibly的區(qū)別及說(shuō)明
多線程lock與lockInterruptibly區(qū)別
在多線程編程中,鎖(Lock)是用來(lái)確保多個(gè)線程在訪問(wèn)共享資源時(shí)能夠保持一致性和正確性的關(guān)鍵工具。
Java 提供了多種實(shí)現(xiàn)鎖的機(jī)制,其中最常用的是 ReentrantLock
。ReentrantLock
提供了兩種獲取鎖的方法:lock
和 lockInterruptibly
。
這兩者在處理線程中斷時(shí)表現(xiàn)不同,理解這些差異對(duì)于編寫健壯的多線程程序至關(guān)重要。
lock 方法
示例代碼:
public static void lock() { Lock lock = new ReentrantLock(); try { lock.lock(); Thread t1 = new Thread(() -> { Thread currentThread = Thread.currentThread(); try { lock.lock(); System.out.println(currentThread.getName() + " lock后的代碼"); } finally { lock.unlock(); } System.out.println("currentThread = " + currentThread.getName() + " isInterrupted:" + currentThread.isInterrupted()); }, "t1"); t1.start(); t1.interrupt(); System.out.println("currentThread = " + Thread.currentThread().getName()); } finally { lock.unlock(); } }
執(zhí)行結(jié)果:
currentThread = main
t1 lock后的代碼
currentThread = t1 isInterrupted:true
解釋:
在這個(gè)示例中,主線程(main
)首先獲取了鎖。然后啟動(dòng)一個(gè)新線程(t1
),該線程嘗試再次獲取同一個(gè)鎖。主線程在啟動(dòng) t1
后立即調(diào)用 t1.interrupt()
方法中斷 t1
線程。
盡管 t1
線程被中斷,它還是成功獲取到了鎖并執(zhí)行了鎖后的代碼。這是因?yàn)?lock
方法在獲取鎖時(shí)不會(huì)理會(huì)線程的中斷狀態(tài),只要鎖可用,它就會(huì)獲取鎖。換句話說(shuō),即使線程被中斷,它也會(huì)繼續(xù)等待獲取鎖,直到成功為止。
lockInterruptibly 方法
示例代碼:
public static void lockInterruptiblyDemo() { Lock lock = new ReentrantLock(); try { lock.lock(); Thread t1 = new Thread(() -> { Thread currentThread = Thread.currentThread(); try { lock.lockInterruptibly(); System.out.println(currentThread.getName() + " lockInterruptibly后的代碼"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } System.out.println("currentThread = " + currentThread.getName() + " isInterrupted:" + currentThread.isInterrupted()); }, "t1"); t1.start(); t1.interrupt(); System.out.println("currentThread = " + Thread.currentThread().getName()); } finally { lock.unlock(); } }
執(zhí)行結(jié)果:
currentThread = main
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at Xxx.lambda$lockInterruptiblyDemo$1(Xxx.java:39)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "t1" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
at Xxx.lambda$lockInterruptiblyDemo$1(Xxx.java:44)
at java.lang.Thread.run(Thread.java:748)
解釋:
在這個(gè)示例中,t1
線程嘗試獲取鎖時(shí)調(diào)用了 lockInterruptibly
方法。與 lock
方法不同,lockInterruptibly
在嘗試獲取鎖時(shí)會(huì)檢查線程的中斷狀態(tài)。如果線程被中斷,它會(huì)立即拋出 InterruptedException
異常,而不會(huì)繼續(xù)等待獲取鎖。
執(zhí)行結(jié)果顯示,t1
線程在嘗試獲取鎖時(shí)被中斷,并且拋出了 InterruptedException
。由于異常被捕獲并打印,t1
線程并沒(méi)有獲取鎖,這避免了在某些情況下可能出現(xiàn)的死鎖問(wèn)題。
區(qū)別總結(jié)
中斷響應(yīng):
lock
方法:不響應(yīng)中斷,線程會(huì)一直嘗試獲取鎖,直到成功為止。lockInterruptibly
方法:響應(yīng)中斷,線程在檢測(cè)到中斷后會(huì)拋出InterruptedException
并停止嘗試獲取鎖。
使用場(chǎng)景:
lock
方法適用于不需要考慮中斷的場(chǎng)景。lockInterruptibly
方法適用于需要及時(shí)響應(yīng)中斷的場(chǎng)景,例如當(dāng)需要能夠中斷線程來(lái)避免死鎖時(shí)。
面試題:為什么 AQS 中的隊(duì)列設(shè)計(jì)為雙向鏈表?
AQS(AbstractQueuedSynchronizer)是 Java 并發(fā)包中鎖和同步器的基礎(chǔ)框架。AQS 內(nèi)部使用了一個(gè) FIFO(先進(jìn)先出)的雙向鏈表來(lái)管理等待線程的隊(duì)列。
原因:
雙向遍歷:
- 當(dāng)一個(gè)節(jié)點(diǎn)(線程)被喚醒時(shí),需要能方便地找到前驅(qū)節(jié)點(diǎn)以確保線程的喚醒順序和正確性。
- 雙向鏈表允許從當(dāng)前節(jié)點(diǎn)輕松訪問(wèn)前驅(qū)節(jié)點(diǎn),從而能夠修改前驅(qū)節(jié)點(diǎn)的狀態(tài)。
節(jié)點(diǎn)刪除:
- 在一些情況下,節(jié)點(diǎn)(線程)可能需要從隊(duì)列中取消。
- 雙向鏈表使得刪除節(jié)點(diǎn)操作更加高效,因?yàn)榭梢灾苯油ㄟ^(guò)前驅(qū)和后繼節(jié)點(diǎn)來(lái)重新鏈接,而不需要從頭遍歷。
狀態(tài)更新:
- 雙向鏈表結(jié)構(gòu)使得更新節(jié)點(diǎn)狀態(tài)(如將節(jié)點(diǎn)從等待狀態(tài)變?yōu)檫\(yùn)行狀態(tài))更為便捷
- 可以在節(jié)點(diǎn)之間高效地傳播狀態(tài)信息
實(shí)現(xiàn)復(fù)雜性:
- 雖然雙向鏈表在實(shí)現(xiàn)上稍微復(fù)雜一些
- 但提供的靈活性和操作效率的提升在高并發(fā)場(chǎng)景中是值得的
通過(guò)這種設(shè)計(jì),AQS 能夠更高效地管理線程隊(duì)列,確保鎖和同步器的高性能和正確性。
詳細(xì)分析:AQS 中的雙向鏈表
AQS 是一個(gè)基于節(jié)點(diǎn)的框架,所有等待獲取鎖的線程都會(huì)被封裝成一個(gè)節(jié)點(diǎn)并加入到等待隊(duì)列中。該隊(duì)列的結(jié)構(gòu)和操作直接影響鎖的性能和響應(yīng)能力。
AQS 中的主要節(jié)點(diǎn)結(jié)構(gòu):
每個(gè)節(jié)點(diǎn)包含以下主要部分:
- 前驅(qū)節(jié)點(diǎn):指向前一個(gè)等待線程的節(jié)點(diǎn)。
- 后繼節(jié)點(diǎn):指向后一個(gè)等待線程的節(jié)點(diǎn)。
- 線程引用:包含正在等待獲取鎖的線程的引用。
- 狀態(tài):表示線程的等待狀態(tài)(如等待、取消等)。
操作示例:
入隊(duì):
- 當(dāng)一個(gè)線程請(qǐng)求獲取鎖但鎖不可用時(shí),它會(huì)被封裝成一個(gè)節(jié)點(diǎn)并加入到隊(duì)列末尾。
- 雙向鏈表結(jié)構(gòu)允許快速定位隊(duì)尾并將新節(jié)點(diǎn)鏈接上去。
出隊(duì):
- 當(dāng)一個(gè)節(jié)點(diǎn)被喚醒時(shí)(通常是獲取到鎖),它需要從隊(duì)列中移除。
- 雙向鏈表結(jié)構(gòu)允許通過(guò)前驅(qū)和后繼節(jié)點(diǎn)高效地重新鏈接,刪除節(jié)點(diǎn)。
狀態(tài)傳播:
- 當(dāng)一個(gè)節(jié)點(diǎn)狀態(tài)改變時(shí)(如從等待到運(yùn)行),它需要通知前驅(qū)或后繼節(jié)點(diǎn)。
- 雙向鏈表允許在節(jié)點(diǎn)之間高效地傳播狀態(tài),確保隊(duì)列中的每個(gè)線程都能正確響應(yīng)狀態(tài)變化。
總結(jié)
理解 lock
和 lockInterruptibly
的區(qū)別有助于我們?cè)诙嗑€程編程中選擇適當(dāng)?shù)逆i獲取方式,確保程序的健壯性和響應(yīng)能力。而 AQS 中的雙向鏈表設(shè)計(jì)則是確保鎖和同步器高效管理等待線程隊(duì)列的重要基礎(chǔ),這種設(shè)計(jì)使得線程管理更加高效和靈活,在高并發(fā)場(chǎng)景中表現(xiàn)尤為突出。通過(guò)深入理解這些底層機(jī)制,我們可以更好地編寫高性能的多線程應(yīng)用程序。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java文件字符輸入流FileReader讀取txt文件亂碼的解決
這篇文章主要介紹了Java文件字符輸入流FileReader讀取txt文件亂碼的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09Java?數(shù)據(jù)結(jié)構(gòu)進(jìn)階二叉樹(shù)題集下
二叉樹(shù)可以簡(jiǎn)單理解為對(duì)于一個(gè)節(jié)點(diǎn)來(lái)說(shuō),最多擁有一個(gè)上級(jí)節(jié)點(diǎn),同時(shí)最多具備左右兩個(gè)下級(jí)節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)。本文將帶你通過(guò)實(shí)際題目來(lái)熟練掌握2022-04-04spring MVC中接口參數(shù)解析的過(guò)程詳解
這篇文章主要給大家介紹了關(guān)于spring MVC中接口參數(shù)解析的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring mvc具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07MyBatis 添加元數(shù)據(jù)自定義元素標(biāo)簽的實(shí)現(xiàn)代碼
這篇文章主要介紹了MyBatis 添加元數(shù)據(jù)自定義元素標(biāo)簽的實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07java實(shí)現(xiàn)表格數(shù)據(jù)的存儲(chǔ)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)表格數(shù)據(jù)的存儲(chǔ),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-04SpringBoot中controller深層詳細(xì)講解
這篇文章主要介紹了SpringBoot在Controller層接收參數(shù)的常用方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02利用Intellij Idea連接遠(yuǎn)程服務(wù)器實(shí)現(xiàn)遠(yuǎn)程上傳部署功能
大家在使用Intellij Idea開(kāi)發(fā)程序的時(shí)候,是不是需要部署到遠(yuǎn)程SSH服務(wù)器運(yùn)行呢,當(dāng)然也可以直接在idea軟件內(nèi)容實(shí)現(xiàn)配置部署操作,接下來(lái)通過(guò)本文給大家分享利用Intellij Idea連接遠(yuǎn)程服務(wù)器實(shí)現(xiàn)遠(yuǎn)程上傳部署功能,感興趣的朋友跟隨小編一起看看吧2021-05-05