Java锁——死锁

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lzx_2011/article/details/73694941

死锁

死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

产生条件

java 死锁产生的四个必要条件:

1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

死锁示例

有两个共享资源object1,object2,我们知道每个对象内部都有一个监视器锁,当一个线程占有资源 object1,保持不释放,并申请资源 object2;而另一个线程占有资源 object2,保持不释放,并申请资源 object1;这样就会导致两个线程一直在循环等待,产生死锁。

/**
 * ClassName: MyDeadLock <br/>
 * Function: 死锁示例<br/>
 *
 * @author gary.liu
 * @date 2017/6/24
 */
public class MyDeadLock {

    private static Object object1 = new Object();
    private static Object object2 = new Object();

    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    static class Worker implements Runnable {

        public void run() {
            synchronized (object1) {

                System.out.println("Worker has object1 lock!");
                /**
                 *
                 * 不睡一会的话,由于两个线程的运行时间可能不是同时的,就会有一个先运行,获取两个资源后释放,一个后运行,此时可能不会产生死锁;
                 * 如果线程几乎同时开始运行,则就有可能产生死锁,加这个 sleep 时间就是让死锁产生的更明显.
                 *
                 * 为了证明上面的说法,使用 CountDownLatch 让两个线程同时运行来看看结果
                 *
                 */
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (object2) {
                    //由于产生死锁,无法输出
                    System.out.println("Worker has object2 lock!");
                }
            }


        }
    }

    static class Boss implements Runnable {

        public void run() {
            synchronized (object2) {

                System.out.println("Boss has object2 lock!");

                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (object1) {
                    //由于产生死锁,无法输出
                    System.out.println("Boss has object1 lock!");

                }
            }
        }
    }

    public static void main(String[] args) throws Exception{

        Thread worker = new Thread(new Worker());
        Thread boss = new Thread(new Boss());

        worker.start();
        boss.start();

    }

}

运行结果

Worker has object1 lock!
Boss has object2 lock!
//两个线程一直在循环等待获取对方资源,产生死锁,程序还一直在运行,但没有内容输出了

代码中的注释:
不睡一会的话,由于两个线程的运行时间可能不是同时的,就会有一个先运行,一个后运行,此时可能不会产生死锁;如果线程几乎同时开始运行,则就有可能产生死锁,加这个等待时间就是让死锁更有机会产生. 为了证明上面的说法,使用 CountDownLatch 让两个线程同时运行来看看结果,不用 sleep 一会,基本每次都会产生死锁。

两个线程同时运行,加大产生死锁的机会

public class MyDeadLock {

    private static Object object1 = new Object();
    private static Object object2 = new Object();

    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    static class Worker implements Runnable {

        public void run() {
            synchronized (object1) {

                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }

                System.out.println("Worker has object1 lock!");

                synchronized (object2) {
                    //由于产生死锁,无法输出
                    System.out.println("Worker has object2 lock!");
                }
            }
        }
    }

    static class Boss implements Runnable {

        public void run() {
            synchronized (object2) {

                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("Boss has object2 lock!");

                synchronized (object1) {
                    //由于产生死锁,无法输出
                    System.out.println("Boss has object1 lock!");

                }
            }
        }
    }

    public static void main(String[] args) throws Exception{

        Thread worker = new Thread(new Worker());
        Thread boss = new Thread(new Boss());

       //因为用countDownLatch.await();两个线程被阻塞了
        worker.start();
        boss.start();

        //更清楚的看到 worker,boss 线程被阻塞
       Thread.sleep(3000); 
       //之后两个线程开始同时继续执行,基本每次都会产生死锁
       countDownLatch.countDown();  
    }

}

分析死锁

1、jps 查看程序进程 id,即 pid
2、jstack pid 打印堆栈信息,可以从堆栈信息中看到 Thread-1 和 Thread-0 在互相等待对方释放资源。
这里写图片描述

具体分析参考:Java多线程7:死锁

或者用 jdk 自带的命令 jvisualvm 工具,对程序的运行进行监控,可以看到线程监控中已经检测到死锁,如下图。
这里写图片描述

避免死锁

破坏上面产生死锁的四个必要条件

遵循以下原则有助于规避死锁:

1、只在必要的最短时间内持有锁,考虑使用同步语句块代替整个同步方法;
2、尽量编写不在同一时刻需要持有多个锁的代码,如果不可避免,则确保线程持有第二个锁的时间尽量短暂;
3、创建和使用一个大锁来代替若干小锁,并把这个锁用于互斥,而不是用作单个对象的对象级别锁;
4、破坏循环等待条件,可以使用 Lock 类中的 tryLock 方法去尝试获取锁,这个方法可以指定一个超时时限,在等待超过该时限之后变回返回一个失败信息;也可以使用信号量,指定去获取的超时时间。

参考资料

Java 实例 - 死锁及解决方法
Java多线程7:死锁

猜你喜欢

转载自blog.csdn.net/lzx_2011/article/details/73694941