每日一例 | 一种死锁的情况……
共 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
关键字分别对变量a
和b
加锁,b
锁位于a
锁内部,在获取b
资源前,先休眠2
秒;在第二个线程内,我们也是通过synchronized
关键字分别对b
和a
进行加锁,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 -