【Java并发】死锁

tech2022-08-04  150

文章目录

什么是死锁发生死锁的条件死锁的修复实际工程中如何避免死锁以哲学家就餐问题为例解决方案 有效避免的方法: 其他活跃性问题活锁饥饿 面试问题

什么是死锁

什么是死锁: 发生在并发中,两者互不相让,互相持有对方所需要的资源,又不主动释放,导致程序卡死。

死锁的影响 死锁的影响在不同的系统中不同的。在数据库中就是可以检测并且放弃事务的。但是JVM无法自动处理死锁。

死锁发生的几率不高但是危害大,压力测试无法找出所有的潜在死锁。

发生死锁的条件

互斥条件请求与保持条件不剥夺条件循环等地条件。

图解Java并发设计认为三点

存在多个资源块线程持有某个资源块时还希望获得其他的资源块获取资源的顺序并不固定。

死锁的修复

可以采用ThreadMXBean这一工具类。

ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); long[] deadlockedThreads = threadMXBean.findDeadlockedThreads(); if (deadlockedThreads != null && deadlockedThreads.length>0){ for (int i = 0; i < deadlockedThreads.length; i++) { ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]); System.out.println("find "+threadInfo.getThreadName()); } }

实际工程中如何避免死锁

线上问题都需要防患于未然,无法线上及时的修复首先需要保存现场,保存堆栈的信息,然后重启服务器等。首先保证用户的体验。暂时保证线上代码的安全, 利用保存的堆栈信息等,排查死锁,修改代码。

修复策略:

避免策略:哲学家就餐,设置一致的锁的获取顺序。检测与恢复策略:每隔一段时间检测是否有死锁,如果有就剥夺某一个资源,打开死锁。鸵鸟策略:直到发生死锁,再进行修复。

以哲学家就餐问题为例

参考

public class test { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); int sum = 5; Chopstick[] chopsticks = new Chopstick[sum]; for (int i = 0; i < sum; i++) { chopsticks[i] = new Chopstick(); } for (int i = 0; i < sum; i++) { exec.execute(new Philosopher(chopsticks[i], chopsticks[(i + 1) % sum])); } } } // 筷子 class Chopstick { public Chopstick() { } } class Philosopher implements Runnable { private Chopstick left; private Chopstick right; public Philosopher(Chopstick left, Chopstick right) { this.left = left; this.right = right; } @Override public void run() { try { while (true) { Thread.sleep(100);//思考一段时间 System.out.println(Thread.currentThread().getName() + " " + "Think"); synchronized (left) { System.out.println(Thread.currentThread().getName() + " " + "left"); synchronized (right) { System.out.println(Thread.currentThread().getName() + " " + "right"); Thread.sleep(1000);//进餐一段时间 } } } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }

解决方案

服务员检查(避免策略)改变一个哲学的拿起叉子的顺序, 给叉子进行编号,每次拿起小的。餐票解决,最多n-1个餐票检测与恢复策略,发生死锁之后检测到死锁,要求一个线程释放资源。 检测与恢复策略:逐个终止线程,直到死锁消除;资源抢占。 终止顺序:1. 优先级,前台交互还是后台处理;2. 已占用资源,还需要的资源。3. 运行时间。 线程回退几步,但是可能导致线程饥饿。

有效避免的方法:

8个经验:

设置超时时间:tryLock(1000,TimeUnit.MILLISECONDS)。synchronized无法尝试锁。获取锁失败再尝试发邮件,报警等。多使用并发类,不要自己设置锁。ConcurrentHashMap,ConcurrentLinkedQueue,AtomicBoolean尽量较低锁的使用粒度和临界区。如果可以使用同步代码块优先使用。给线程起有意义的名字。避免锁的嵌套。分配资源前考虑能不能收回,银行家算法不要几个功能用同一把锁。

其他活跃性问题

活锁

持有锁之后一段时间放开锁,再重新获得锁。可能正好还是卡死,编程活锁。 活锁的特点是线程没有被阻塞,但是没有进展。活锁还要持续消耗cpu资源。双方相互谦让导致谁都无法持有锁。

引入随机重试,解决活锁问题。

饥饿

线程需要资源但是无法得到资源。 优先级的设置需要注意。

面试问题

实际中的死锁,线程需要持有多个锁的场合,锁的嵌套。如何定位死锁,ThreadMXBean找到死锁。jstack类。解决死锁的策略。
最新回复(0)