多線程-lock與lockInterruptibly的區(qū)別及說明
多線程lock與lockInterruptibly區(qū)別
在多線程編程中,鎖(Lock)是用來確保多個線程在訪問共享資源時能夠保持一致性和正確性的關鍵工具。
Java 提供了多種實現(xiàn)鎖的機制,其中最常用的是 ReentrantLock
。ReentrantLock
提供了兩種獲取鎖的方法:lock
和 lockInterruptibly
。
這兩者在處理線程中斷時表現(xià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í)行結果:
currentThread = main
t1 lock后的代碼
currentThread = t1 isInterrupted:true
解釋:
在這個示例中,主線程(main
)首先獲取了鎖。然后啟動一個新線程(t1
),該線程嘗試再次獲取同一個鎖。主線程在啟動 t1
后立即調(diào)用 t1.interrupt()
方法中斷 t1
線程。
盡管 t1
線程被中斷,它還是成功獲取到了鎖并執(zhí)行了鎖后的代碼。這是因為 lock
方法在獲取鎖時不會理會線程的中斷狀態(tài),只要鎖可用,它就會獲取鎖。換句話說,即使線程被中斷,它也會繼續(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í)行結果:
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)
解釋:
在這個示例中,t1
線程嘗試獲取鎖時調(diào)用了 lockInterruptibly
方法。與 lock
方法不同,lockInterruptibly
在嘗試獲取鎖時會檢查線程的中斷狀態(tài)。如果線程被中斷,它會立即拋出 InterruptedException
異常,而不會繼續(xù)等待獲取鎖。
執(zhí)行結果顯示,t1
線程在嘗試獲取鎖時被中斷,并且拋出了 InterruptedException
。由于異常被捕獲并打印,t1
線程并沒有獲取鎖,這避免了在某些情況下可能出現(xiàn)的死鎖問題。
區(qū)別總結
中斷響應:
lock
方法:不響應中斷,線程會一直嘗試獲取鎖,直到成功為止。lockInterruptibly
方法:響應中斷,線程在檢測到中斷后會拋出InterruptedException
并停止嘗試獲取鎖。
使用場景:
lock
方法適用于不需要考慮中斷的場景。lockInterruptibly
方法適用于需要及時響應中斷的場景,例如當需要能夠中斷線程來避免死鎖時。
面試題:為什么 AQS 中的隊列設計為雙向鏈表?
AQS(AbstractQueuedSynchronizer)是 Java 并發(fā)包中鎖和同步器的基礎框架。AQS 內(nèi)部使用了一個 FIFO(先進先出)的雙向鏈表來管理等待線程的隊列。
原因:
雙向遍歷:
- 當一個節(jié)點(線程)被喚醒時,需要能方便地找到前驅節(jié)點以確保線程的喚醒順序和正確性。
- 雙向鏈表允許從當前節(jié)點輕松訪問前驅節(jié)點,從而能夠修改前驅節(jié)點的狀態(tài)。
節(jié)點刪除:
- 在一些情況下,節(jié)點(線程)可能需要從隊列中取消。
- 雙向鏈表使得刪除節(jié)點操作更加高效,因為可以直接通過前驅和后繼節(jié)點來重新鏈接,而不需要從頭遍歷。
狀態(tài)更新:
- 雙向鏈表結構使得更新節(jié)點狀態(tài)(如將節(jié)點從等待狀態(tài)變?yōu)檫\行狀態(tài))更為便捷
- 可以在節(jié)點之間高效地傳播狀態(tài)信息
實現(xiàn)復雜性:
- 雖然雙向鏈表在實現(xiàn)上稍微復雜一些
- 但提供的靈活性和操作效率的提升在高并發(fā)場景中是值得的
通過這種設計,AQS 能夠更高效地管理線程隊列,確保鎖和同步器的高性能和正確性。
詳細分析:AQS 中的雙向鏈表
AQS 是一個基于節(jié)點的框架,所有等待獲取鎖的線程都會被封裝成一個節(jié)點并加入到等待隊列中。該隊列的結構和操作直接影響鎖的性能和響應能力。
AQS 中的主要節(jié)點結構:
每個節(jié)點包含以下主要部分:
- 前驅節(jié)點:指向前一個等待線程的節(jié)點。
- 后繼節(jié)點:指向后一個等待線程的節(jié)點。
- 線程引用:包含正在等待獲取鎖的線程的引用。
- 狀態(tài):表示線程的等待狀態(tài)(如等待、取消等)。
操作示例:
入隊:
- 當一個線程請求獲取鎖但鎖不可用時,它會被封裝成一個節(jié)點并加入到隊列末尾。
- 雙向鏈表結構允許快速定位隊尾并將新節(jié)點鏈接上去。
出隊:
- 當一個節(jié)點被喚醒時(通常是獲取到鎖),它需要從隊列中移除。
- 雙向鏈表結構允許通過前驅和后繼節(jié)點高效地重新鏈接,刪除節(jié)點。
狀態(tài)傳播:
- 當一個節(jié)點狀態(tài)改變時(如從等待到運行),它需要通知前驅或后繼節(jié)點。
- 雙向鏈表允許在節(jié)點之間高效地傳播狀態(tài),確保隊列中的每個線程都能正確響應狀態(tài)變化。
總結
理解 lock
和 lockInterruptibly
的區(qū)別有助于我們在多線程編程中選擇適當?shù)逆i獲取方式,確保程序的健壯性和響應能力。而 AQS 中的雙向鏈表設計則是確保鎖和同步器高效管理等待線程隊列的重要基礎,這種設計使得線程管理更加高效和靈活,在高并發(fā)場景中表現(xiàn)尤為突出。通過深入理解這些底層機制,我們可以更好地編寫高性能的多線程應用程序。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Java文件字符輸入流FileReader讀取txt文件亂碼的解決
這篇文章主要介紹了Java文件字符輸入流FileReader讀取txt文件亂碼的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09MyBatis 添加元數(shù)據(jù)自定義元素標簽的實現(xiàn)代碼
這篇文章主要介紹了MyBatis 添加元數(shù)據(jù)自定義元素標簽的實現(xiàn)代碼,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07利用Intellij Idea連接遠程服務器實現(xiàn)遠程上傳部署功能
大家在使用Intellij Idea開發(fā)程序的時候,是不是需要部署到遠程SSH服務器運行呢,當然也可以直接在idea軟件內(nèi)容實現(xiàn)配置部署操作,接下來通過本文給大家分享利用Intellij Idea連接遠程服務器實現(xiàn)遠程上傳部署功能,感興趣的朋友跟隨小編一起看看吧2021-05-05