Java并发:隐藏线程死锁
大多數(shù)Java程序員熟悉Java線程死鎖概念。 它本質(zhì)上涉及2個(gè)線程,它們彼此永遠(yuǎn)等待。 這種情況通常是平面(同步)或ReentrantLock(讀或?qū)?#xff09;鎖排序問題的結(jié)果。
好消息是,HotSpot JVM始終可以為您檢測(cè)到這種情況……還是嗎? 最近一個(gè)影響Oracle Service Bus生產(chǎn)環(huán)境的線程死鎖問題迫使我們重新審視此經(jīng)典問題并確定是否存在“隱藏”死鎖情況。 本文將通過一個(gè)簡(jiǎn)單的Java程序演示并復(fù)制非常特殊的鎖排序死鎖條件,最新的HotSpot JVM 1.7并未檢測(cè)到該情況。 您還可以在本文結(jié)尾處找到一個(gè)視頻 ,向您介紹Java示例程序和所使用的故障排除方法。
犯罪現(xiàn)場(chǎng)
我通常喜歡將主要的Java并發(fā)問題與犯罪現(xiàn)場(chǎng)進(jìn)行比較,在犯罪現(xiàn)場(chǎng)您扮演首席調(diào)查員的角色。 在這種情況下,“犯罪”是客戶IT環(huán)境的實(shí)際生產(chǎn)中斷。 您的工作是:
- 收集所有證據(jù),提示和事實(shí)(線程轉(zhuǎn)儲(chǔ),日志,業(yè)務(wù)影響,負(fù)載數(shù)字…)
- 詢問證人和領(lǐng)域?qū)<?#xff08;支持團(tuán)隊(duì),交付團(tuán)隊(duì),供應(yīng)商,客戶……)
調(diào)查的下一步是分析收集的信息,并建立一個(gè)或多個(gè)“可疑”的潛在清單以及清晰的證據(jù)。 最終,您希望將其范圍縮小到主要可疑或根本原因。 顯然,“直到證明有罪之前無罪”的法律在這里并不適用,恰恰相反。 缺乏證據(jù)可能會(huì)阻止您實(shí)現(xiàn)上述目標(biāo)。 接下來,您將看到Hotspot JVM缺少死鎖檢測(cè)并沒有必要證明您沒有在處理此問題。
犯罪嫌疑人
在此故障排除上下文中,“可疑”定義為具有以下有問題的執(zhí)行模式的應(yīng)用程序或中間件代碼。
- 使用FLAT鎖,然后使用ReentrantLock WRITE鎖(執(zhí)行路徑#1)
- 使用ReentrantLock READ鎖,然后使用FLAT鎖(執(zhí)行路徑#2)
- 由2個(gè)Java線程并發(fā)執(zhí)行,但執(zhí)行順序相反
上面的鎖排序死鎖標(biāo)準(zhǔn)可以如下所示:
現(xiàn)在,讓我們通過示例Java程序來復(fù)制此問題,并查看JVM線程轉(zhuǎn)儲(chǔ)輸出。
示例Java程序
上面的死鎖條件是首先從我們的Oracle OSB問題案例中發(fā)現(xiàn)的。 然后,我們通過一個(gè)簡(jiǎn)單的Java程序重新創(chuàng)建了它。 您可以在此處 下載我們程序的完整源代碼。 該程序只是創(chuàng)建并觸發(fā)2個(gè)工作線程。 它們每個(gè)執(zhí)行不同的執(zhí)行路徑,并嘗試以不同的順序獲取共享對(duì)象上的鎖。 我們還創(chuàng)建了一個(gè)死鎖檢測(cè)器線程以進(jìn)行監(jiān)視和記錄。 現(xiàn)在,在下面找到實(shí)現(xiàn)2條不同執(zhí)行路徑的Java類。
package org.ph.javaee.training8;import java.util.concurrent.locks.ReentrantReadWriteLock;/*** A simple thread task representation* @author Pierre-Hugues Charbonneau**/ public class Task {// Object used for FLAT lockprivate final Object sharedObject = new Object();// ReentrantReadWriteLock used for WRITE & READ locksprivate final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();/*** Execution pattern #1*/public void executeTask1() {// 1. Attempt to acquire a ReentrantReadWriteLock READ locklock.readLock().lock();// Wait 2 seconds to simulate some work...try { Thread.sleep(2000);}catch (Throwable any) {}try { // 2. Attempt to acquire a Flat lock...synchronized (sharedObject) {}}// Remove the READ lockfinally {lock.readLock().unlock();} System.out.println("executeTask1() :: Work Done!");}/*** Execution pattern #2*/public void executeTask2() {// 1. Attempt to acquire a Flat locksynchronized (sharedObject) { // Wait 2 seconds to simulate some work...try { Thread.sleep(2000);}catch (Throwable any) {}// 2. Attempt to acquire a WRITE lock lock.writeLock().lock();try {// Do nothing}// Remove the WRITE lockfinally {lock.writeLock().unlock();}}System.out.println("executeTask2() :: Work Done!");}public ReentrantReadWriteLock getReentrantReadWriteLock() {return lock;} }一旦觸發(fā)死鎖情況,就會(huì)使用JVisualVM生成JVM線程轉(zhuǎn)儲(chǔ)。
從Java線程轉(zhuǎn)儲(chǔ)示例中可以看到。 JVM未檢測(cè)到此死鎖條件(例如,不存在“發(fā)現(xiàn)一個(gè)Java級(jí)死鎖”),但很明顯,這兩個(gè)線程處于死鎖狀態(tài)。
根本原因:ReetrantLock READ鎖定行為
至此,我們發(fā)現(xiàn)的主要解釋與ReetrantLock READ鎖的用法有關(guān)。 讀鎖通常不設(shè)計(jì)為具有所有權(quán)概念。 由于沒有哪個(gè)線程持有讀取鎖的記錄,因此這似乎可以防止HotSpot JVM死鎖檢測(cè)器邏輯檢測(cè)到涉及讀鎖的死鎖。 從那時(shí)起,我們進(jìn)行了一些改進(jìn),但是我們可以看到JVM仍然無法檢測(cè)到這種特殊的死鎖情況。 現(xiàn)在,如果我們用寫鎖替換程序中的讀鎖(執(zhí)行模式2),那么JVM將最終檢測(cè)到死鎖情況,但是為什么呢?
Found one Java-level deadlock: ============================= "pool-1-thread-2":waiting for ownable synchronizer 0x272239c0, (a java.util.concurrent.locks.ReentrantReadWriteLock$NonfairSync),which is held by "pool-1-thread-1" "pool-1-thread-1":waiting to lock monitor 0x025cad3c (object 0x272236d0, a java.lang.Object),which is held by "pool-1-thread-2"Java stack information for the threads listed above: =================================================== "pool-1-thread-2":at sun.misc.Unsafe.park(Native Method)- parking to wait for <0x272239c0> (a java.util.concurrent.locks.ReentrantReadWriteLock$NonfairSync)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)at java.util.concurrent.locks.AbstractQueuedSynchronizer. parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:834)at java.util.concurrent.locks.AbstractQueuedSynchronizer. acquireQueued(AbstractQueuedSynchronizer.java:867)at java.util.concurrent.locks.AbstractQueuedSynchronizer. acquire(AbstractQueuedSynchronizer.java:1197)at java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock.lock(ReentrantReadWriteLock.java:945)at org.ph.javaee.training8.Task.executeTask2(Task.java:54)- locked <0x272236d0> (a java.lang.Object)at org.ph.javaee.training8.WorkerThread2.run(WorkerThread2.java:29)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)at java.lang.Thread.run(Thread.java:722) "pool-1-thread-1":at org.ph.javaee.training8.Task.executeTask1(Task.java:31)- waiting to lock <0x272236d0> (a java.lang.Object)at org.ph.javaee.training8.WorkerThread1.run(WorkerThread1.java:29)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)at java.lang.Thread.run(Thread.java:722)這是因?yàn)轭愃朴谄矫骀i,JVM跟蹤寫入鎖。 這意味著HotSpot JVM死鎖檢測(cè)器似乎當(dāng)前被設(shè)計(jì)用來檢測(cè):
- 對(duì)象監(jiān)視器上涉及FLAT鎖的死鎖
- 死鎖涉及與WRITE鎖相關(guān)聯(lián)的已鎖定擁有的同步器
缺少讀取鎖每線程跟蹤似乎可以防止這種情況下的死鎖檢測(cè),并大大增加了故障排除的復(fù)雜性。 我建議您閱讀Doug Lea在整個(gè)問題上的評(píng)論 ,因?yàn)樵缭?005年就開始擔(dān)心由于某些潛在的鎖定開銷而可能添加每線程讀取保持跟蹤。 如果您懷疑涉及讀取鎖的隱藏死鎖情況,請(qǐng)?jiān)谙旅娴墓收吓懦ㄗh中查找:
- 仔細(xì)分析線程調(diào)用堆棧跟蹤,它可能會(huì)揭示某些代碼潛在地獲取讀鎖,并阻止其他線程獲取寫鎖。
- 如果您是代碼的所有者,請(qǐng)通過使用lock.getReadLockCount()方法來跟蹤讀取鎖的計(jì)數(shù)。
我期待著您的反饋,特別是對(duì)于具有此類涉及讀鎖的死鎖經(jīng)驗(yàn)的個(gè)人。 最后,在下面的視頻中找到通過示例Java程序的執(zhí)行和監(jiān)視來解釋這些發(fā)現(xiàn)的視頻。
參考: Java并發(fā)性: Java EE支持模式和Java教程博客中,我們的JCG合作伙伴 Pierre-Hugues Charbonneau 隱藏的線程死鎖 。
翻譯自: https://www.javacodegeeks.com/2013/02/java-concurrency-the-hidden-thread-deadlocks.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的Java并发:隐藏线程死锁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中世纪精致风 RPG 游戏《Mirthw
- 下一篇: JavaFX:太空侵略者在175 LOC