死锁

作者: 俗人浮生 | 来源:发表于2019-03-19 20:07 被阅读0次

涉及到多线程并发编程,总会涉及到“死锁”的问题,这里说件比较尴尬的事,笔者有一次去面试,被面试官给绕晕了,结果人家一句:“你这不就死锁了吗?”把我吓出一身汗来···
今天,我们先来手写一段死锁的代码:

public class MyObject {
    private final Object A=new Object();
    private final Object B=new Object();
    public void a(){
        synchronized (A){
            System.out.println(Thread.currentThread().getName()+" 持有A对象锁!");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (B){
                System.out.println("a已完成!");
            }
        }
    }
    public void b() {
        synchronized (B){
            System.out.println(Thread.currentThread().getName()+" 持有B对象锁!");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (A){
                System.out.println("b已完成!");
            }
        }
    }
}

如上,我们定义了一个类,其中有两个成员对象,同时定义了两个方法,每个方法里都有锁的嵌套,接着,我们来进行调用:

    public static void main(String[] args) {
        final MyObject myObject=new MyObject();
        Thread threadA=new Thread(){
            @Override
            public void run() {
                super.run();
                myObject.a();
            }
        };
        Thread threadB=new Thread(){
            @Override
            public void run() {
                super.run();
                myObject.b();
            }
        };
        threadA.start();
        threadB.start();
    }

好啦,就这么简单,这样就是一个最简单的死锁形式了,我们来运行一下:

Thread-0持有A对象锁!
Thread-1持有B对象锁!

结果如上,我们永远都等不到“a已完成!”和“b已完成!”
其实,整个过程并不复杂,threadA持有myObject中A对象锁,threadB持有myObject中B对象锁,而a方法要完成需要拿到myObject中B对象锁,b方法要完成需要拿到myObject中A对象锁,很明显,如果a方法不执行完毕,其不会释放myObject中A对象锁,而如果b方法不执行完毕,其不会释放myObject中B对象锁,就这样,大家都互相等下去耗下去——死锁。

如果我们将调用的代码改为如下:

    public static void main(String[] args) {
        final MyObject myObjectA=new MyObject();
        final MyObject myObjectB=new MyObject();
        Thread threadA=new Thread(){
            @Override
            public void run() {
                super.run();
                myObjectA.a();
            }
        };
        Thread threadB=new Thread(){
            @Override
            public void run() {
                super.run();
                myObjectB.b();
            }
        };
        threadA.start();
        threadB.start();
    }

运行结果:

Thread-1持有B对象锁!
Thread-0持有A对象锁!
a已完成!
b已完成!

这个也没什么可说的,因为是不同的对象,不存在死锁的问题。
那么,如果是同一对象,我们有什么办法来解决上面死锁的问题呢?
办法当然是有的,我们可以用Lock类中的tryLock()方法去获取锁,该方法会返回获取锁的结果,也支持设置获取锁的超时时限,如果获取不到锁,我们可以进行相应的处理,而不至于造成死锁,下面我们对例子进行更改:

public class MyObject2 {
    class ObjectA {
        public Lock lock = new ReentrantLock();
    }
    class ObjectB {
        public Lock lock = new ReentrantLock();
    }
    private final ObjectA A=new ObjectA();
    private final ObjectB B=new ObjectB();
    public void a(){
        if(A.lock.tryLock()){
            System.out.println(Thread.currentThread().getName()+"持有A对象锁!");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(B.lock.tryLock()){
                System.out.println(Thread.currentThread().getName()+"持有B对象锁!");
                B.lock.unlock();//释放B对象锁
                System.out.println("a已完成!");
            }else {
                System.out.println(Thread.currentThread().getName()+"获取B对象锁失败!");
            }
            A.lock.unlock();//释放A对象锁
        }else {
            System.out.println(Thread.currentThread().getName()+"获取A对象锁失败!");
        }
    }
    public void b() {
        if(B.lock.tryLock()){
            System.out.println(Thread.currentThread().getName()+"持有B对象锁!");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(A.lock.tryLock()){
                System.out.println(Thread.currentThread().getName()+"持有A对象锁!");
                A.lock.unlock();//释放A对象锁
                System.out.println("b已完成!");
            }else {
                System.out.println(Thread.currentThread().getName()+"获取A对象锁失败!");
            }
            B.lock.unlock();//释放B对象锁
        }else {
            System.out.println(Thread.currentThread().getName()+"获取B对象锁失败!");
        }
    }
}

调用代码同上面,只需把MyObject更换为MyObject2即可,这里就不再展示,直接看看运行结果:

Thread-0持有A对象锁!
Thread-1持有B对象锁!
Thread-1获取A对象锁失败!
Thread-0获取B对象锁失败!

恩,这样总算不至于死锁了,起码程序可以正常执行下去,如果你将a()方法中获取B对象的锁代码更改为:B.lock.tryLock(1,TimeUnit.SECONDS),那么运行结果为:

Thread-0持有A对象锁!
Thread-1持有B对象锁!
Thread-1获取A对象锁失败!
Thread-0持有B对象锁!
a已完成!

很显然,因为a()获取B对象锁设置了超时时间为1秒,而在b()方法中释放lock后,a()得以获取到B对象锁,所以可以执行完毕。

总结来说,Lock和synchronized有以下几点不同:(参考该文
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

相关文章

  • 死锁

    线程饥饿死锁 锁顺序死锁 动态锁顺序死锁通过锁顺序来避免死锁 避免死锁

  • 死锁

    第11章:死锁和进程通信 死锁概念 死锁处理方法 死锁预防(Deadlock Prevention) 死锁避免(D...

  • java多线程笔记

    产生死锁的四个必要条件 处理死锁的基本方法 死锁预防 死锁避免 死锁检测 死锁解除 https://blog.cs...

  • [现代操作系统]--死锁

    table of content 死锁定义 死锁建模-- 资源分配图 处理死锁鸵鸟算法检测并恢复死锁检测死锁恢复利...

  • Java-多线程(四)死锁

    死锁 死锁示例

  • Java死锁

    什么是死锁 死锁检测 产生死锁的四个必要条件 如何避免死锁 死锁 死锁,指两个或多个线程之间,由于互相持有对方需要...

  • java并发--java死锁

    本篇结构: 前言 什么是死锁 产生死锁的必要条件 死锁的代码示例 死锁排查 如何避免死锁 总结 一、前言 今天被问...

  • Java多线程之死锁(Deadlock)及死锁避免(Deadlo

    线程死锁(Thread Deadlock) 数据库死锁(Database Deadlocks) 死锁避免 (Dea...

  • JavaEE面试题总结 Day39 2018-12-29

    什么是线程死锁?死锁如何产生?如何避免线程死锁? 死锁的介绍: 线程死锁是指由于两个或者多个线程互相持有对方所需要...

  • Java并发之嵌套管程锁死(Nested Monitor Loc

    嵌套管程死锁是如何发生的 具体的嵌套管程死锁的例子 嵌套管程死锁 vs 死锁 嵌套管程锁死类似于死锁, 下面是一个...

网友评论

      本文标题:死锁

      本文链接:https://www.haomeiwen.com/subject/kqprmqtx.html