死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

Java线程中我们构造两个相互等待对方释放资源的线程就构成了死锁。

代码:

public class DeadLockSimulation {
    private static String A = "a";
    private static String B = "b";

    public static void main(String[] args) {
        new DeadLockSimulation().deadLock();
    }

    private void deadLock() {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (A) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (B) {
                        System.out.println("t1");
                    }
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (B) {
                    synchronized (A) {
                        System.out.println("t2");
                    }
                }
            }
        });

        t1.start();
        t2.start();
    }
}

运行上边的代码产生死锁。JDK自带的工具有jconsolejvisualvmjstack等。

jvisualvm

运行jvisualvm如下:

发现提示检测到死锁。

jstack

查看进程号:

jps // 查看Java任务进程号
2784 Jps
8672 Main
18068 Launcher
12520 KotlinCompileDaemon
16776 DeadLockSimulation
9576
16668 RemoteMavenServer

运行jstack。

jstack [进程号] // 查看当前进程堆栈

Found one Java-level deadlock:                                                                           
=============================                                                                            
"Thread-0":                                                                           
  waiting to lock monitor 0x0000012f11b53e80 (object 0x0000000741d763e8, a java.lang.String),            
  which is held by "Thread-1"                                                        
"Thread-1":                                                          
  waiting to lock monitor 0x0000012f11b51e80 (object 0x0000000741d76400, a java.lang.String),            
  which is held by "Thread-0"                                                        
Java stack information for the threads listed above:                                                     
===================================================                                   
"Thread-0":                                                                                              
        at com.lin.juc.mydesign.DeadLockSimulation$1.run(DeadLockSimulation.java:28)                     
        - waiting to lock <0x0000000741d763e8> (a java.lang.String)                                      
        - locked <0x0000000741d76400> (a java.lang.String)                                               
        at java.lang.Thread.run([email protected]/Thread.java:834)                                        
"Thread-1":                                                                          
        at com.lin.juc.mydesign.DeadLockSimulation$2.run(DeadLockSimulation.java:39)                     
        - waiting to lock <0x0000000741d76400> (a java.lang.String)                  
        - locked <0x0000000741d763e8> (a java.lang.String)                           
        at java.lang.Thread.run([email protected]/Thread.java:834)                   
Found 1 deadlock.          

MXBean

MXBeanJDK自带的用于扫描程序是否存在死锁包, 但是扫描的过程中存在性能损耗。

代码:

public class ScanDeadLock {
    public void scanDeadLock() {
        ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
        Runnable runnable = () -> {
            long[] ids = mxBean.findDeadlockedThreads();
            System.out.println("扫描死锁...");
            if (ids != null) {
                ThreadInfo[] threadInfos = mxBean.getThreadInfo(ids);
                for (ThreadInfo info : threadInfos) {
                    System.out.println("info = " + info);
                }
            }
        };
        ExecutorService executorService = Executors.newScheduledThreadPool(4);
        executorService.execute(runnable);
    }
}

死锁预防

  • 避免一个线程同时获取多个锁。
  • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
  • 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
  • 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。