java线程死锁_Java并发:隐藏线程死锁
java線程死鎖
大多數Java程序員熟悉Java線程死鎖概念。 它本質上涉及2個線程,它們彼此永遠等待。 這種情況通常是平面(同步)或ReentrantLock(讀或寫)鎖排序問題的結果。
好消息是,HotSpot JVM始終可以為您檢測到這種情況……還是嗎? 最近一個影響Oracle Service Bus生產環境的線程死鎖問題迫使我們重新審視此經典問題并確定“隱藏”死鎖情況的存在。 本文將通過一個簡單的Java程序演示并復制非常特殊的鎖順序死鎖條件,最新的HotSpot JVM 1.7并未檢測到該情況。 您還可以在本文結尾處找到一個視頻 ,向您介紹Java示例程序以及所使用的故障排除方法。
犯罪現場
我通常喜歡將主要的Java并發問題與犯罪現場進行比較,在犯罪現場您扮演首席調查員的角色。 在這種情況下,“犯罪”是客戶IT環境的實際生產中斷。 您的工作是:
- 收集所有證據,提示和事實(線程轉儲,日志,業務影響,負載數字…)
- 詢問證人和領域專家(支持團隊,交付團隊,供應商,客戶...)
調查的下一步是分析收集的信息,并建立一個或多個“嫌疑人”的潛在清單以及明確的證據。 最終,您希望將其范圍縮小到主要可疑或根本原因。 顯然,“直到證明有罪之前無罪”的法律在這里并不適用,恰恰相反。 缺乏證據可能會阻止您實現上述目標。 接下來,您將看到Hotspot JVM缺少死鎖檢測,并沒有必要證明您沒有解決此問題。
犯罪嫌疑人
在此故障排除上下文中,“可疑”定義為具有以下有問題的執行模式的應用程序或中間件代碼。
- 使用FLAT鎖,然后使用ReentrantLock WRITE鎖(執行路徑#1)
- 使用ReentrantLock READ鎖,然后使用FLAT鎖(執行路徑#2)
- 由2個Java線程并發執行,但執行順序相反
上面的鎖排序死鎖標準可以如下所示:
現在,讓我們通過示例Java程序來復制此問題,并查看JVM線程轉儲輸出。
示例Java程序
上面的死鎖條件是首先從我們的Oracle OSB問題案例中發現的。 然后,我們通過一個簡單的Java程序重新創建了它。 您可以在此處 下載我們程序的完整源代碼。 該程序只是創建并觸發2個工作線程。 它們每個執行不同的執行路徑,并嘗試以不同的順序獲取共享對象上的鎖。 我們還創建了一個死鎖檢測器線程以進行監視和記錄。 現在,在下面找到實現2條不同執行路徑的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;} }一旦觸發死鎖情況,就會使用JVisualVM生成JVM線程轉儲。
從Java線程轉儲示例中可以看到。 JVM沒有檢測到此死鎖條件(例如,不存在“發現一個Java級死鎖”),但是很明顯,這兩個線程處于死鎖狀態。
根本原因:ReetrantLock READ鎖定行為
至此,我們發現的主要解釋與ReetrantLock READ鎖的用法有關。 讀取鎖通常不設計為具有所有權概念。 由于沒有哪個線程持有讀取鎖的記錄,因此這似乎可以防止HotSpot JVM死鎖檢測器邏輯檢測到涉及讀鎖的死鎖。 從那時起就實現了一些改進,但是我們可以看到JVM仍然無法檢測到這種特殊的死鎖情況。 現在,如果我們用寫鎖替換程序中的讀鎖(執行模式2),那么JVM將最終檢測到死鎖情況,但是為什么呢?
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)這是因為JVM跟蹤寫入鎖類似于平面鎖。 這意味著HotSpot JVM死鎖檢測器似乎當前被設計用來檢測:
- 對象監視器上涉及FLAT鎖的死鎖
- 死鎖涉及與WRITE鎖關聯的鎖定的可擁有同步器
缺少讀取鎖每線程跟蹤似乎可以防止這種情況下的死鎖檢測,并大大增加了故障排除的復雜性。 我建議您閱讀Doug Lea在整個問題上的評論 ,因為早在2005年,人們就開始擔心由于某些潛在的鎖定開銷而可能增加按線程讀取保持跟蹤的可能性。 如果您懷疑涉及讀取鎖的隱藏死鎖情況,請在下面的故障排除建議中查找:
- 仔細分析線程調用堆棧跟蹤,它可能會揭示某些代碼潛在地獲取讀鎖,并阻止其他線程獲取寫鎖。
- 如果您是代碼的所有者,請通過使用lock.getReadLockCount()方法來跟蹤讀取鎖的計數。
我期待著您的反饋,特別是對于具有此類涉及讀鎖的死鎖經驗的個人。 最后,在下面的視頻中找到通過示例Java程序的執行和監視來解釋這些發現的視頻。
參考: Java并發性: Java EE支持模式和Java教程博客中,我們的JCG合作伙伴 Pierre-Hugues Charbonneau 隱藏的線程死鎖 。
翻譯自: https://www.javacodegeeks.com/2013/02/java-concurrency-the-hidden-thread-deadlocks.html
java線程死鎖
總結
以上是生活随笔為你收集整理的java线程死锁_Java并发:隐藏线程死锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为手机价格大全2022价格表(华为手机
- 下一篇: pr音频拉大快捷键(pr音频放大快捷键)