并发主题

隐藏的线程死锁

有一些死锁可能是由于 flat (synchronized) 或 ReentrantLock (读或写)  lock-ordering 锁顺序问题引起的,比如我们得到threaddump输出,就知道发生死锁。

Found one Java-level deadlock:
=============================
"pool-1-thread-2":
waiting to lock monitor 0x0237ada4 (object 0x272200e8, a java.lang.Object),
which is held by "pool-1-thread-1"
"pool-1-thread-1":
waiting to lock monitor 0x0237aa64 (object 0x272200f0, a java.lang.Object),
which is held by "pool-1-thread-2"

但是在一些情况下,JVM并不能发现死锁,这种死锁情况通常发生如下情况:

  • FLAT锁后面跟着ReentrantLock WRITE 写锁 (execution path #1)
  • ReentrantLock READ锁后面跟着FLAT锁 (execution path #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 lock
       private final Object sharedObject = new Object();
       // ReentrantReadWriteLock used for WRITE & READ locks
       private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
     
       /**
        *  Execution pattern #1
        */
       public void executeTask1() {
           
             // 1. Attempt to acquire a ReentrantReadWriteLock READ lock
             lock.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 lock
             finally {
                    lock.readLock().unlock();
             }          
           
             System.out.println("executeTask1() :: Work Done!");
       }
     
       /**
        *  Execution pattern #2
        */
       public void executeTask2() {
           
             // 1. Attempt to acquire a Flat lock
             synchronized (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 lock
                    finally {
                           lock.writeLock().unlock();
                    }
             }
           
             System.out.println("executeTask2() :: Work Done!");
       }
     
       public ReentrantReadWriteLock getReentrantReadWriteLock() {
             return lock;
       }
}

JVisualVM的threadDump输出:

JVM并没有发现死锁 (e.g. 没有报警:found one Java-level deadlock) ,但是很清楚这个两个线程陷入了死锁状态。

主要原因是ReetrantLock READ 锁的使用,read锁通常不会设计为一个所有权的概念,因为并没有记录去保存读锁。这就阻止JVM侦听包括读锁的死锁逻辑,如果我们使用写锁替代读锁,JVM就会发现死锁,这是因为写锁被JVM作为flat 锁一样跟踪.。

下面方式有助于排出隐藏的死锁:

  • 密切线程调用堆栈跟踪分析,它可能揭示了一些代码,可能获取读锁定并防止其他线程获取写锁
  • 如果你为自己写代码,用lock.getReadLockCount()方法读取锁定计数跟踪。

 

死锁详解研究

java多线程

Java同步或锁

Java性能调优