线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于相互等待状态,若无外力作用,它们将无法继续执行下去。
造成死锁的原因可以概括成三句话:
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
// 线程1
new Thread(() -> {
synchronized (a) {
System.out.println("获得了A锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
}
}
}).start();
// 线程2
new Thread(() -> {
synchronized (b) {
System.out.println("获得了B锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
}
}
}).start();
}
上面的程序就是一个典型死锁的例子,为了保证死锁发生的几率,我这里在获得锁之后睡眠了1s。
线程1在获得A对象锁之后等了1s去尝试获取B对象锁,这时线程1是持有A对象锁
的;线程2在获得B对象锁之后等待1s去尝试获得A对象锁,这时线程2是持有B对象锁
的;就在它们彼此想获得对方的锁的时候,死锁
发生了,并且一直持续下去。
上面提到只有这四个条件都发生时才会出现死锁,那么意思就是说,只要我们破坏其中一个,就可以成功预防死锁的发生。
一次性申请
所有的资源,这样就不存在等待了。主动释放
它占有的资源。synchronized
关键字,它是不能主动释放资源的,会造成线程一直阻塞,JUC提供了Lock
解决这个问题。检测死锁
和从死锁中恢复过来。显式锁可以指定一个超时时限(Timeout),在等待超过该时间后tryLock就会返回一个失败信息,释放其拥有的资源,其他线程可以获取此资源避免死锁。交叉占用
的情况即可,也就是说在在代码中尽量避免线程1保持A请求B,线程2保持B请求A,尽可能使他们请求的顺序一致,比如线程1请求的顺序是A、B,线程2请求的顺序也是A、B,这样自然就避免了循环等待的情况发生。死锁是一个比较头疼的问题,但是只要我们的代码规范,可以避免大多数情况下的死锁。还有避免死锁的经典算法是银行家算法,这里就不扩开介绍了。
在很多情况下,尤其是多线程编程中,我们要注意线程之间的资源是否存在互相竞争的情况,如果有,要及时规避死锁的风险。
死锁很多时候会发生在数据库操作中,例如长事务、并发条件下的共享锁升级等都会造成数据库死锁,后面有时间会专门针对数据库死锁讲一讲。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://lebron.blog.csdn.net/article/details/125669764
内容来源于网络,如有侵权,请联系作者删除!