• Java程序的死锁


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

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

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

    举例分析

    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

     

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

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

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

    分析的思路大概就是

    区分线程状态 -> 查看等待目标 -> 对比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都是独占的,要么是我用,要么是你用。

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

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

    第一种方法

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

    第二种方法

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

    银行家算法

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

     

           

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

    第三种方法

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

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

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

    第四种方法

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

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

    技巧

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

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

    参考杨晓峰老师的《java核心技术》

    一个没有高级趣味的人。 email:hushui502@gmail.com
  • 相关阅读:
    Loved
    什么是REST
    统一资源定位符URL(Uniform Resource Locator)
    HTTP工作原理
    系统程序员成长计划内存管理(一)
    系统程序员成长计划工程管理(四)
    系统程序员成长计划-内存管理(四)
    HTTP请求报文格式
    系统程序员成长计划内存管理(二)
    系统程序员成长计划-内存管理(三)
  • 原文地址:https://www.cnblogs.com/CherryTab/p/12184571.html
Copyright © 2020-2023  润新知