什么情况下Java程序会产生死锁?

 

循环依赖,彼此一直处于等待状态,并且互相都没有进展。死锁不仅仅是在线程之间会发生,存在资源独占的进程之间同样 也可能出现死锁。通常来说,我们大多是聚焦在多线程场景中的死锁,指两个或多个线程之间,由于互相持有对方需要的锁,而永久处于阻塞的状态。

 

java进程linux Java进程锁_System

 

 

 

如果程序运行时发生了死锁,绝大多数情况下都是无法在线解决的,只能重启、修正程序本身问题。所以,代码开发阶段互相审查,或者利用工具进行预防性排查,往往也是很重要的。

 

举例分析

package Test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class DeadThread extends Thread{
    private Object obj1;
    private Object obj2;
    private int order;

    public DeadThread(int order, Object obj1, Object obj2) {
        this.order = order;
        this.obj1 = obj1;
        this.obj2 = obj2;
    }

    public void test1() throws InterruptedException {
        synchronized (obj1) {
            //建议线程调取器切换到其它线程运行
            Thread.yield();
            synchronized (obj2) {
                System.out.println("test。。。");
            }

        }
    }
    public void test2() throws InterruptedException {
        synchronized (obj2) {
            Thread.yield();
            synchronized (obj1) {
                System.out.println("test。。。");
            }

        }
    }

    @Override
    public void run() {

        while (true) {
            try {
                if(this.order == 1){
                    this.test1();
                }else{
                    this.test2();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class Test {
    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        Object obj2 = new Object();

        ExecutorService ex = Executors.newFixedThreadPool(10);
        // 起10个线程
        for (int i = 0; i < 10; i++) {
            int order = i%2==0 ? 1 : 0;
            ex.execute(new DeadThread(order, obj1, obj2));
        }
    }
}

这段就是典型的死锁代码,我们如何通过工具而不是人眼识别?  jstack,还有一些图形化工具,这里只讲前者

 

用jstack发现死锁

首先jps,这个命令就是显示java的进程pid

 

 

 

 

java进程linux Java进程锁_java进程linux_02

 

 

 这是没有运行上面死锁代码的,我们运行一下再jps

java进程linux Java进程锁_java进程linux_03

 

 

 可以看出相关pid为11100,然后我们再用jstack分析

java进程linux Java进程锁_System_04

 

 

 可以看出分析的结果,然后我们就可以从相关位置处查看分析了。

 

分析的思路大概就是

区分线程状态 -> 查看等待目标 -> 对比Monitor等持有状态

 

用ThreadMXBean来发现死锁

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

public class DeadLockTesting {

    public static void main(String args[]) {
        Object obj1 = new Object();
        Object obj2 = new Object();

        DeadLockThread t1 = new DeadLockThread(obj1, obj2);
        DeadLockThread t2 = new DeadLockThread(obj2, obj1);

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

        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        }

        MonitorThread mt = new MonitorThread();
        mt.start();
    }
}

class MonitorThread extends Thread {
    public void run() {
        ThreadMXBean tmx = ManagementFactory.getThreadMXBean();
        long[] ids = tmx.findDeadlockedThreads();
        if (ids != null) {
            ThreadInfo[] infos = tmx.getThreadInfo(ids, true, true);
            System.out.println("The following threads are deadlocked:");
            for (ThreadInfo ti : infos) {
                System.out.println(ti);
            }
        }
    }
}

class DeadLockThread extends Thread {
    private Object obj1;
    private Object obj2;

    public DeadLockThread(Object obj1, Object obj2) {
        this.obj1 = obj1;
        this.obj2 = obj2;
    }

    public void run() {
        synchronized (obj1) {
            try {
                Thread.sleep(1000);
                synchronized (obj2) {

                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

 

 

如何在编程中尽量预防死锁呢?

互斥条件,类似Java中Monitor都是独占的,要么是我用,要么是你用。

互斥条件是长期持有的,在使用结束之前,自己不会释放,也不能被其他线程抢占。

循环依赖关系,两个或者多个个体之间出现了锁的链条环。

 

第一种方法

如果可能的话,尽量避免使用多个锁,并且只有需要时才持有锁。

 

第二种方法

如果必须使用多个锁,尽量设计好锁的获取顺序,这个说起来简单,做起来可不容易,你可以参看著名的银行家算法。

银行家算法

java进程linux Java进程锁_java_05

 

 

将对象(方法)和锁之间的关系,用图形化的方式表示分别抽取出来,以今天最初讲的死锁为例,因为是调用了同一个线程所以更加简单。

 

java进程linux Java进程锁_System_06

 

 

 

java进程linux Java进程锁_java_07

      

java进程linux Java进程锁_死锁_08

 

 

 

 很清晰就发现这有死锁的风险

 

第三种方法

使用带超时的方法,为程序带来更多可控性。

类似Object.wait(…)或者CountDownLatch.await(…),都支持所谓的timed_wait,我们完全可以就不假定该锁一定会获得,指定超时时间,并为无法得到锁时准备退出逻辑。

if (lock.tryLock() || lock.tryLock(timeout, unit)) {  // ...  }

 

第四种方法

 

业界也有一些其他方面的尝试,比如通过静态代码分析(如FindBugs)去查找固定的模式,进而定位可能的死锁或者竞争情况。

findbugs是一个好用的插件,可以自行搜索使用

 

技巧

比如Linux上,可以使用top命令配合grep Java之类,找到忙的pid;然后,转换成16进制,就是jstack输出中的格式;再定位代码

 

任务线程规范命名,详细记录逻辑运行日志。jstack查看线程状态。