为了让小白也能看懂这个死锁Case,我请来了小黑...

开发 前端
我相信稍微仔细点大家都能发现这个问题,这是因为我把干扰项都排除,只留下非常简单的框架。如果在一个非常复杂的系统中,还是很难发现的。这也是我今天遇到的一个线上问题,花了半天时间才排查出来。

小黑有点困,他想休息,又怕耽误时间,于是准备小眯一会。

为了能按时起来,他设了闹钟,作为程序员,必须得整两个,防止单点故障。

当任意一个闹钟响起,小黑就起来把两个闹钟都关掉,继续干活,就像这样:

public class Clock {
    private BlackBro blackBro;

    public void setBlackBro(BlackBro blackBro) {
        this.blackBro = blackBro;
    }

    public synchronized void ring() {
        System.out.println(Thread.currentThread() + " Clock.ring...");
        blackBro.wake();
    }

    public synchronized void close() {
        System.out.println(Thread.currentThread() + " Clock.close...");
    }
}
public class BlackBro {
    private Clock[] clocks;

    public void setClocks(Clock[] clocks) {
        this.clocks = clocks;
    }

    public synchronized void wake() {
        System.out.println(Thread.currentThread() + "BlackBro.wake...");
        for (Clock clock : clocks) {
            clock.close();
        }
    }
}

为了防止闹钟和小黑在执行操作期间被人打扰,我贴心地给他们都加上了锁 —— synchronized。

模拟这个场景将是这样:

public static void main(String[] args) {
    Clock clock1 = new Clock();
    Clock clock2 = new Clock();
    BlackBro blackBro = new BlackBro();

    clock1.setBlackBro(blackBro);
    clock2.setBlackBro(blackBro);
    blackBro.setClocks(new Clock[]{clock1, clock2});

    // sleep...
    Thread t1 = new Thread(clock1::ring);
    Thread t2 = new Thread(clock2::ring);

    t1.start();
    t2.start();
}

启动程序发现,陷入了无尽地等待:

Thread[Thread-0,5,main] Clock.ring...
Thread[Thread-1,5,main] Clock.ring...
Thread[Thread-1,5,main]BlackBro.wake...

这是怎么回事?眼尖的同学肯定发现问题了。我们看一下 jstack:

Found one Java-level deadlock:
=============================
"Thread-0":
  waiting to lock monitor 0x0000600003ecc000 (object 0x000000070fc52398, a com.demo.BlackBro),
  which is held by "Thread-1"

"Thread-1":
  waiting to lock monitor 0x0000600003ec04e0 (object 0x000000070fc50f88, a com.demo.Clock),
  which is held by "Thread-0"

Java stack information for the threads listed above:
===================================================

原来是死锁了:我们起了两个闹钟线程,两个线程各自拿到自己的对象锁,开始 ring,ring 又都会去唤醒小黑,但小黑对象只有一个,只有一个闹钟能顺利拿到小黑的对象锁,小黑被唤醒后又去关闹钟,但却没法关掉,因为闹钟在等小黑唤醒的期间不会被别人打断,于是闹钟在等小黑,小黑在等闹钟,形成了死锁。

我相信稍微仔细点大家都能发现这个问题,这是因为我把干扰项都排除,只留下非常简单的框架。如果在一个非常复杂的系统中,还是很难发现的。这也是我今天遇到的一个线上问题,花了半天时间才排查出来。

这个 case 教育我们要谨慎使用锁,尤其是 synchronized;其次如果发现程序没有按预期地执行,尤其是该执行的没执行,可以留个心眼,看看堆栈是不是有死锁。

2024 年第一个小case送给你,你学废了吗?

责任编辑:武晓燕 来源: 捉虫大师
相关推荐

2017-02-22 15:04:52

2021-11-01 15:15:37

Context项目代码

2019-12-27 09:47:05

大数据TomcatWeb

2018-12-24 08:46:52

Kubernetes对象模型

2019-11-04 13:09:43

数据平台架构

2022-10-11 08:27:45

Spring事务管理性能统计

2020-02-15 17:16:05

Kubernetes容器

2019-11-18 10:38:03

线程池Java框架

2019-10-10 11:10:04

SpringBoot异步编程

2013-09-22 10:34:08

码农机器学习算法

2019-03-26 11:15:34

AI机器学习人工智能

2020-03-31 10:36:07

数据平台架构

2013-03-15 10:35:17

编程语言编程笑话

2020-11-16 16:38:30

人工智能AI

2018-05-16 10:07:02

监控报警系统

2022-12-27 16:31:22

AI绘图软件

2018-03-06 10:38:23

云计算大数据人工智能

2017-11-02 12:08:56

2018-11-19 08:34:22

Hadoop架构HDFS

2022-07-04 08:31:42

GitOpsGit基础设施
点赞
收藏

51CTO技术栈公众号