java死锁定位与预防

死锁

死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

死锁发生的原因

死锁的发生是由于资源竞争导致的,导致死锁的原因如下:

  • 系统资源不足,如果系统资源充足,死锁出现的可能性就很低。
  • 进程(线程)运行推进的顺序不合适。
  • 资源分配不当等。

死锁发生的条件

死锁的发生的四个必要条件:

  • 互斥条件:一个资源每次只能被一个进程使用。
  • 占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不可强行占有:进程(线程)已获得的资源,在未使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程(线程)之间形成一种头尾相接的循环等待资源关系。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

死锁的例子

为了说明死锁,我们下面举一个例子来说明:
这里写图片描述

线程A持有资源A然后试图获取资源B,线程2持有资源B同时试图获取资源A,这样线程A和B都不能获取彼此想要的资源,导致整个程序不能向下推进。具体代码如下:

class DeadLockRunnable implements Runnable {

    private String first;
    private String second;

    public DeadLockRunnable(String first, String second) {
        this.first = first;
        this.second = second;
    }

    @Override
    public void run() {
        synchronized (first) {
            System.out.println(Thread.currentThread().getName() + "obtain: " + first);
            try {
                Thread.sleep(100);
                synchronized (second) {
                    System.out.println(Thread.currentThread().getName() + " obtain: " + first);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class DeadLock {
    public static void main(String[] args) throws InterruptedException {
            String lockA = "lockA";
            String lockB = "LockB";

            Thread t1 = new Thread(new DeadLockRunnable(lockA, lockB));
            Thread t2 = new Thread(new DeadLockRunnable(lockB, lockA));
            t1.start();
            t2.start();
            t1.join();
            t2.join();
    }
}

运行结果:
这里写图片描述

从结果中可以看出,两个线程都不能再次获得想要的资源,程序一直处于“停滞”状态。

死锁的定位与预防

那么出现这种情况怎么定位呢?

  • 通过jdk提供的命令
    我们可以使用jstack或者类似Jconsole等图形化工具,查看线程的状态。上面程序jstack输出如下:
    这里写图片描述

通过输出我们看到Thread-1和Thread-0都处于BLOCKED状态。至此我们基本可以知道程序发生了死锁。

  • 分析CPU占用情况
    根据死锁的定义,我们可以得出死锁一定会导致有线程处于”饥饿“状态而一直占用CPU时间片。因此可以通过排查出使用CPU时间片最高的线程,再打出该线程的堆栈信息,最后排查代码找出死锁的原因。

死锁的预防

  • 尽量避免使用多个锁,并且只有需要时才持有锁。
  • 如果使用多个锁,一定要设计好锁的获取顺序。
  • 使用带有超时的方法,为程序带来更多的可控性,比如指定获取锁的时间最多为5秒,超时就放弃。
  • 通过一些代码静态检查工具发现可能存在的死锁问题,比如FindBugs。

猜你喜欢

转载自blog.csdn.net/cl2010abc/article/details/80779662