说明:本文介绍如何写自旋逻辑,解决死锁问题。
死锁问题
死锁,指多个进程都在等对方释放资源,才能继续执行,以致进程阻塞。
产生死锁有以下四个必要条件:
-
互斥条件:进程获取的资源具有排他性。“我拿了,别人就拿不了”
-
请求与保持:进程获取到资源,后进程阻塞,不会释放已有资源。“我拿了,不做完事情,我就不放下”
-
不可剥夺:进程获取到资源,其他进程不可强制剥夺。“我拿在手上,别人抢不走”
-
循环等待:进程没有获取到所需资源,会持续等待,最终产生了死锁。“你等我,我等他,他等你”
如何避免死锁,只需破坏必要条件中的一个即可,如:
-
互斥条件:锁的作用就是让进程前互斥,这个条件没法破坏,用锁作用就是这个,除非不用锁,不用锁也就不存在死锁问题。
-
请求与保持:让进程一次性申请到所有资源,即将进程所需的所有资源打包成一个。
-
不可剥夺:进程获取资源,进一步申请其他资源时,如果申请不到,要求进程主动释放已有资源。
-
循环等待:申请资源按照顺序申请,释放资源按照逆序释放,目的是保证任何时间都不会有进程持有非顺序的资源,就是说,争抢下个资源的前提是已获取到所需的资源。
场景
博主之前遇到过一个问题,在DAO访问数据库这里,产生了死锁
orderDetailRepository.updateOrder(orderId, status);
该操作是一个更新多条数据库记录的操作,而这张表是一张记录流水的表,会频繁写入数据,博主猜想可能是两个进程,一个写入,一个更新,产生了死锁。
自旋
后通过自旋等待,解决了此问题(进程争抢资源都是毫秒级别的,死锁问题很罕见,加入自旋等待后就没出现过问题了,我也无法确定问题是否解决)
如下:
// 这里可能出现死锁,如果出现死锁,自旋重试
// 初始重试次数
int retryCount = 3;
// 初始等待时间(毫秒)
int sleepTime = 100;
while (retryCount > 0) {
try {
orderDetailMapper.updateOrder(orderId, status);
break;
} catch (DeadlockLoserDataAccessException e) {
retryCount--;
if (retryCount == 0) {
throw e;
}
// 记录死锁日志
log.warn("产生死锁, 等待重试... 重试次数: {}", retryCount);
try {
TimeUnit.MILLISECONDS.sleep(sleepTime);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
// 每次等待时间翻倍
sleepTime *= 2;
}
}
总结
本文介绍项目中产生进程死锁,手写自旋进程等待,解决死锁问题的实现。