• 谈谈java中的死锁


    什么是死锁

    在使用多线程以及多进程时,两个或两个以上的运算单元(进程、线程或协程),各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,就称为死锁

    下面看个简单的例子:

    public class DeadLockTest {
        public static void main(String[] args) {
            Object lock1 = new Object(); // 锁1
            Object lock2 = new Object(); // 锁2
    
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    // 先获取锁1
                    synchronized (lock1) {
                        System.out.println("Thread 1:获取到锁1!");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //获取锁2
                        System.out.println("Thread 1:等待获取2...");
                        synchronized (lock2) {
                            System.out.println("Thread 1:获取到锁2!");
                        }
                    }
                }
            });
            t1.start(); 
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    // 先获取锁2
                    synchronized (lock2) {
                        System.out.println("Thread 2:获取到锁2!");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // 获取锁1
                        System.out.println("Thread 2:等待获取1...");
                        synchronized (lock1) {
                            System.out.println("Thread 2:获取到锁1!");
                        }
                    }
                }
            });
            t2.start(); 
        }
    }

    死锁产生原因

    死锁只有同时满足以下四个条件才会发生:

    互斥条件:线程对所分配到的资源具有排它性,在某个时间内锁资源只能被一个线程占用。如下图:

    线程1已经持有的资源,不能再同时被线程2持有,如果线程2请求获取线程1已经占用的资源,那线程2只能等待,直到线程1释放资源。如下图:

    持有并请求条件:

    持有并请求条件是指,当线程1已经持有了资源1,又想申请资源2,而资源2已经被线程3持有了,所以线程1就会处于等待状态,但是线程1在等待资源2的同时并不会释放已经持有的资源1。

    不可剥夺条件:当线程已经持有了资源 ,在未使用完之前,不能被剥夺。如下图:

    线程2如果也想使用此资源,只能等待线程1使用完并释放后才能获取。

    环路等待条件:在死锁发生的时候,两个线程获取资源的顺序构成了环形链,如下图:

    线程1已经持有资源2,想获取资源1, 线程2已经获取了资源1,想请求资源2,这就形成资源请求等待的环形图。

    死锁的排查

    1.jstack

    用法如下:

    /opt/java8/bin/jstack
    
    Usage:
        jstack [-l] <pid>
            (to connect to running process) 连接活动线程
        jstack -F [-m] [-l] <pid>
            (to connect to a hung process) 连接阻塞线程
        jstack [-m] [-l] <executable> <core>
            (to connect to a core file) 连接dump的文件
        jstack [-m] [-l] [server_id@]<remote server IP or hostname>
            (to connect to a remote debug server) 连接远程服务器
    
    Options:
        -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
        -m  to print both java and native frames (mixed mode)
        -l  long listing. Prints additional information about locks
        -h or -help to print this help message

    死锁日志如下:

    "mythread2" #12 prio=5 os_prio=0 tid=0x0000000058ef7800 nid=0x1ab4 waiting on condition [0x0000000059f8f000]
       java.lang.Thread.State: WAITING (parking)
            at sun.misc.Unsafe.park(Native Method)
            - parking to wait for  <0x00000000d602d610> (a java.util.concurrent.lock
    s.ReentrantLock$NonfairSync)
            at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
            at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt
    errupt(AbstractQueuedSynchronizer.java:836)
            at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A
    bstractQueuedSynchronizer.java:870)
            at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac
    tQueuedSynchronizer.java:1199)
            at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo
    ck.java:209)
            at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
    
            at DeathLock$2.run(DeathLock.java:34)
    
       Locked ownable synchronizers:
            - <0x00000000d602d640> (a java.util.concurrent.locks.ReentrantLock$Nonfa
    irSync)
    
    "mythread1" #11 prio=5 os_prio=0 tid=0x0000000058ef7000 nid=0x3e68 waiting on condition [0x000000005947f000]
       java.lang.Thread.State: WAITING (parking)
            at sun.misc.Unsafe.park(Native Method)
            - parking to wait for  <0x00000000d602d640> (a java.util.concurrent.lock
    s.ReentrantLock$NonfairSync)
            at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
            at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt
    errupt(AbstractQueuedSynchronizer.java:836)
            at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A
    bstractQueuedSynchronizer.java:870)
            at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac
    tQueuedSynchronizer.java:1199)
            at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo
    ck.java:209)
            at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
    
            at DeathLock$1.run(DeathLock.java:22)
    
       Locked ownable synchronizers:
            - <0x00000000d602d610> (a java.util.concurrent.locks.ReentrantLock$Nonfa
    irSync)
    
    
    Found one Java-level deadlock:
    =============================
    "mythread2":
      waiting for ownable synchronizer 0x00000000d602d610, (a java.util.concurrent.l
    ocks.ReentrantLock$NonfairSync),
      which is held by "mythread1"
    "mythread1":
      waiting for ownable synchronizer 0x00000000d602d640, (a java.util.concurrent.l
    ocks.ReentrantLock$NonfairSync),
      which is held by "mythread2"
    
    Java stack information for the threads listed above:
    ===================================================
    "mythread2":
            at sun.misc.Unsafe.park(Native Method)
            - parking to wait for  <0x00000000d602d610> (a java.util.concurrent.lock
    s.ReentrantLock$NonfairSync)
            at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
            at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt
    errupt(AbstractQueuedSynchronizer.java:836)
            at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A
    bstractQueuedSynchronizer.java:870)
            at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac
    tQueuedSynchronizer.java:1199)
            at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo
    ck.java:209)
            at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
    
            at DeathLock$2.run(DeathLock.java:34)
    "mythread1":
            at sun.misc.Unsafe.park(Native Method)
            - parking to wait for  <0x00000000d602d640> (a java.util.concurrent.lock
    s.ReentrantLock$NonfairSync)
            at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
            at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt
    errupt(AbstractQueuedSynchronizer.java:836)
            at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A
    bstractQueuedSynchronizer.java:870)
            at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac
    tQueuedSynchronizer.java:1199)
            at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo
    ck.java:209)
            at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
    
            at DeathLock$1.run(DeathLock.java:22)
    
    Found 1 deadlock.

    可以根据日志输出来直接定位到具体的死锁代码。

    2.jmc

    jmc是Oracle Java Mission Control的缩写,是一个对Java程序进行管理、监控、概要分析和故障排查的工具套件。在JDK的bin目录中,同样是双击启动,如下图所示:

    jmc打开如下:

    需要选择死锁类,右键启动JMX控制台,然后就可以发现死锁和死锁具体信息。

    常用工具还有jconsole,jvisualvm等不再一一介绍,感兴趣的可以自已查看一下。

    避免死锁问题的发生

    死锁分析

    还要看死锁产生的4个条件,只要不满足条件就无法产生死锁了,互斥条件和不可剥夺条件是系统特性无法阻止。只能通过破坏请求和保持条件或者是环路等待条件,从而来解决死锁的问题。

    避免死锁的方案

    一、固定加锁顺序

    即通过有顺序的获取锁,从而避免产生环路等待条件,从而解决死锁问题的。来看环路等待的例子:

    线程1先获取了锁A,再请求获取锁B,线程2与线程1同时执行,线程2先获取锁B,再请求获取锁A,两个线程都先占用了各自的资源(锁A和锁B)之后,再尝试获取对方的锁,从而造成了环路等待问题,最后造成了死锁。

    此时只需将线程1和线程2获取锁的顺序进行统一,也就是线程1和线程2同时执行之后,都先获取锁A,再获取锁B。因为只有一个线程能成功获取到锁A,没有获取到锁A的线程就会等待先获取锁A,此时得到锁A的线程继续获取锁 B,因为没有线程争抢和拥有锁B,那么得到锁A的线程就会顺利的拥有锁B,之后执行相应的代码再将锁资源全部释放,然后另一个等待获取锁A的线程就可以成功获取到锁资源,这样就不会出现死锁的问题了。

    二、开放调用避免死锁

    在协作对象之间发生死锁的场景中,主要是因为在调用某个方法时就需要持有锁,并且在方法内部也调用了其他带锁的方法,如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用,同步代码块最好仅被用于保护那些涉及共享状态的操作。

    三、使用定时锁

    使用显式Lock锁,在获取锁时使用tryLock()方法。当等待超过时限的时候,tryLock()不会一直等待,而是返回错误信息,能够有效避免死锁问题。

    总结

    简单来说,死锁问题的产生是由两个或者以上线程并行执行的时候,争夺资源而互相等待造成的。

    死锁只有同时满足互斥、持有并等待、不可剥夺、环路等待这四个条件的时候才会发生。

    所以要避免死锁问题,就是要破坏其中一个条件即可,最常用的方法就是使用资源有序分配法来破坏环路等待条件。

  • 相关阅读:
    二分查找及各种变体实现 hunter
    限流算法概述 hunter
    第一章 SpringBoot基础入门 hunter
    Java泛型通配符 hunter
    《伯夷列传》的内核:怨是不怨?
    Windows下UAC音频设备调试
    原来你也在这里
    关于生活(2021)
    《史记》精读录
    山水谈
  • 原文地址:https://www.cnblogs.com/shanheyongmu/p/15892774.html
Copyright © 2020-2023  润新知