死锁

2019-03-19  本文已影响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。所以说,在具体使用时要根据适当情况选择。

上一篇 下一篇

猜你喜欢

热点阅读