每日一例 | ​一种死锁的情况……

云中志

共 3776字,需浏览 8分钟

 ·

2021-05-28 14:03

前言

在多线程编程的时候,为了确保同一时间数据状态的唯一性,我们经常会用到锁(lock),它用起来很方便也很简单,但是在某些特定的应用场景之下,它会给你带来很多困惑,,比如死锁的情况,今天我们就来通过一段简单的代码,来看下什么是死锁,以及如何避免死锁。

死锁

我们通过一段代码来模拟下死锁的情况:

public class Example {
    private static String a = "a";
    private static String b = "b";

    public static void main(String[] args) {
        new Example().deadLock();
    }

    private void deadLock() {
        Thread t1 = new Thread(() -> {
            synchronized (a) {
                try {
                    Thread.currentThread().sleep(2000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println("b1");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (b) {
                synchronized (a) {
                    System.out.println("a2");
                }
            }
        });
        t1.start();
        t2.start();
    }

}

上面的代码中,我们定义了两个static变量,在deadLock方法内,分别定义了两个线程,在第一个线程内,我们通过synchronized关键字分别对变量ab加锁,b锁位于a锁内部,在获取b资源前,先休眠2秒;在第二个线程内,我们也是通过synchronized关键字分别对ba进行加锁,a锁位于b锁内,但是没有睡眠。

上面两个线程,运行顺序是这样的:

如果是t1先启动,t1线程启动后会先对a资源加锁,之后t1休眠2秒,在在线程t1启动到休眠结束这段时间中,t2线程启动并对b资源加锁,并尝试获取a的锁,但由于a已经被t1加锁且未释放锁,这时候t2开始等待t1释放资源,一直到t1休眠结束也未获取到资源,这时候t1开始尝试获取b资源的锁,但由于b资源被t2占用,所以t1也必须等待,最终的结果是两个线程都在等待对方释放资源,两个线程都被阻塞,导致死锁。

t2先启动的情况,实际测试中没法复现,但如果把t2放在t1前面start,那是不会导致死锁的,t2执行完很快就释放资源了,所以不会导致线程阻塞,但如果加了睡眠时间,就和t1先启动的情况一样了。

不喜欢看文字的小伙伴,可以看这个时序图,很直观地体现了死锁的情况:

如何避免死锁

既然知道了导致死锁的原因,我们应该如何避免这种情况出现呢?

第一个解决方法——戒贪,简单来说,就是尽量避免一个线程获取多个锁的情况,如果只是对一个资源加锁的话,那自然是不会导致死锁情况出现的;

第二解决方法,就是引入超时机制,也就是采用定时锁,使用lock.tryLock(timeout)替换synchronized

对于,数据库锁,加锁和解锁必须在同一个数据库连接中进行,否则会出现解锁失败的情况。

总结

锁是多线程编程中一个特别有效的工具,很方便,也解决了资源共享的数据同步问题,但如何用好这个工具就是门艺术了,总之一句话就是要多学习,多实践,不断试错,除此之外,没有其他的捷径了。

多线程这块的知识,想要学好学精,不仅要了解jvm的相关知识,还要熟悉处理器层面的知识,山高路远,还得加油呀!

好了,今天就到这里吧

- END -


浏览 29
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报