如何避免死锁?

并发程序一旦死锁,往往我们只能重启应用。解决死锁问题最好的办法就是避免死锁。

死锁发生的条件

  • 互斥,共享资源只能被一个线程占用
  • 占有且等待,线程 t1 已经取得共享资源 s1,尝试获取共享资源 s2 的时候,不释放共享资源 s1
  • 不可抢占,其他线程不能强行抢占线程 t1 占有的资源 s1
  • 循环等待,线程 t1 等待线程 t2 占有的资源,线程 t2 等待线程 t1 占有的资源

避免死锁的方法

对于以上 4 个条件,只要破坏其中一个条件,就可以避免死锁的发生。

对于第一个条件 "互斥"是不能破坏的,因为加锁就是为了保证互斥。

其他三个条件,我们可以尝试

  • 一次性申请所有的资源,破坏 "占有且等待" 条件
  • 占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 "不可抢占" 条件
  • 按序申请资源,破坏 "循环等待" 条件

编程中的最佳实践:

  • 使用 Lock 的 tryLock(long timeout, TimeUnit unit)的方法,设置超时时间,超时可以退出防止死锁
  • 尽量使用并发工具类代替加锁
  • 尽量降低锁的使用粒度
  • 尽量减少同步的代码块

示例

使用管理类一次性申请所有的资源,破坏 "占有且等待" 条件示例

package constxiong.concurrency.a023;import java.util.HashSet;import java.util.Set;/** * 测试 一次性申请所有的资源,破坏 "占有且等待" 条件示例 * @author ConstXiong * @date 2019-09-24 14:04:12 */public class TestBreakLockAndWait {//单例的资源管理类private final static Manger manager = new Manger();//资源1private static Object res1 = new Object();//资源2private static Object res2 = new Object();public static void main(String[] args) {new Thread(() -> {boolean applySuccess = false;while (!applySuccess) {//向管理类,申请res1和res2,申请失败,重试applySuccess = manager.applyResources(res1, res2);if (applySuccess) {try {System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");synchronized (res1) {System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");//休眠 1秒try {Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();}synchronized (res2) {System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");}}} finally {manager.returnResources(res1, res2);//归还资源}} else {System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源失败");//申请失败休眠 200 毫秒后重试try {Thread.sleep(200);} catch (Exception e) {e.printStackTrace();}}}}).start();new Thread(() -> {boolean applySuccess = false;while (!applySuccess) {//向管理类,申请res1和res2,申请失败,重试applySuccess = manager.applyResources(res1, res2);if (applySuccess) {try {System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源成功");synchronized (res2) {System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res1 资源的锁");//休眠 1秒try {Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();}synchronized (res1) {System.out.println("线程:" + Thread.currentThread().getName() + " 获取到 res2 资源的锁");}}} finally {manager.returnResources(res1, res2);//归还资源}} else {System.out.println("线程:" + Thread.currentThread().getName() + " 申请 res1、res2 资源失败");//申请失败休眠 200 毫秒后重试try {Thread.sleep(200);} catch (Exception e) {e.printStackTrace();}}}}).start();}}/** * 资源申请、归还管理类 * @author ConstXiong * @date 2019-09-24 14:10:57 */class Manger {//资源存放集合private Set<Object> resources = new HashSet<Object>();/** * 申请资源 * @param res1 * @param res2 * @return */synchronized boolean applyResources(Object res1, Object res2) {if (resources.contains(res1) || resources.contains(res1)) {return false;} else {resources.add(res1);resources.add(res2);return true;}}/** * 归还资源 * @param res1 * @param res2 */synchronized void returnResources(Object res1, Object res2) {resources.remove(res1);resources.remove(res2);}}

打印结果如下,线程-1 在线程-0 释放完资源后才能成功申请 res1 和 res2 的锁

线程:Thread-0 申请 res1、res2 资源成功线程:Thread-0 获取到 res1 资源的锁线程:Thread-1 申请 res1、res2 资源失败线程:Thread-1 申请 res1、res2 资源失败线程:Thread-1 申请 res1、res2 资源失败线程:Thread-1 申请 res1、res2 资源失败线程:Thread-1 申请 res1、res2 资源失败线程:Thread-0 获取到 res2 资源的锁线程:Thread-1 申请 res1、res2 资源失败线程:Thread-1 申请 res1、res2 资源成功线程:Thread-1 获取到 res1 资源的锁线程:Thread-1 获取到 res2 资源的锁

使用 Lock 的 tryLock() 方法,获取锁失败释放所有资源,破坏 "不可抢占" 条件示例

package constxiong.concurrency.a023;import java.util.Random;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * 测试 占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 "不可抢占" 条件 * @author ConstXiong * @date 2019-09-24 14:50:51 */public class TestBreakLockOccupation {private static Random r = new Random(); private static Lock lock1 = new ReentrantLock();private static Lock lock2 = new ReentrantLock();public static void main(String[] args) {new Thread(() -> {//标识任务是否完成boolean taskComplete = false;while (!taskComplete) {lock1.lock();System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 成功");try {//随机休眠,帮助造成死锁环境try {Thread.sleep(r.nextInt(30));} catch (Exception e) {e.printStackTrace();}//线程 0 尝试获取 lock2if (lock2.tryLock()) {System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 成功");try {taskComplete = true;} finally {lock2.unlock();}} else {System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 失败");}} finally {lock1.unlock();}//随机休眠,避免出现活锁try {Thread.sleep(r.nextInt(10));} catch (Exception e) {e.printStackTrace();}}}).start();new Thread(() -> {//标识任务是否完成boolean taskComplete = false;while (!taskComplete) {lock2.lock();System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock2 成功");try {//随机休眠,帮助造成死锁环境try {Thread.sleep(r.nextInt(30));} catch (Exception e) {e.printStackTrace();}//线程2 尝试获取锁 lock1if (lock1.tryLock()) {System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 成功");try {taskComplete = true;} finally {lock1.unlock();}} else {System.out.println("线程:" + Thread.currentThread().getName() + " 获取锁 lock1 失败");}} finally {lock2.unlock();}//随机休眠,避免出现活锁try {Thread.sleep(r.nextInt(10));} catch (Exception e) {e.printStackTrace();}}}).start();}}

打印结果如下

线程:Thread-0 获取锁 lock1 成功线程:Thread-1 获取锁 lock2 成功线程:Thread-1 获取锁 lock1 失败线程:Thread-1 获取锁 lock2 成功线程:Thread-0 获取锁 lock2 失败线程:Thread-1 获取锁 lock1 成功线程:Thread-0 获取锁 lock1 成功线程:Thread-0 获取锁 lock2 成功

按照一定的顺序加锁,破坏 "循环等待" 条件示例

package constxiong.concurrency.a023;/** * 测试 按序申请资源,破坏 "循环等待" 条件 * @author ConstXiong * @date 2019-09-24 15:26:23 */public class TestBreakLockCircleWait {private static Object res1 = new Object();private static Object res2 = new Object();public static void main(String[] args) {new Thread(() -> {Object first = res1;Object second = res2;//比较 res1 和 res2 的 hashCode,如果 res1 的 hashcode > res2,交换 first 和 second。保证 hashCode 小的对象先加锁if (res1.hashCode() > res2.hashCode()) {first = res2;second = res1;}synchronized (first) {System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + first + " 锁成功");try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}synchronized(second) {System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + second + " 锁成功");}}}).start();new Thread(() -> {Object first = res1;Object second = res2;//比较 res1 和 res2 的 hashCode,如果 res1 的 hashcode > res2,交换 first 和 second。保证 hashCode 小的对象先加锁if (res1.hashCode() > res2.hashCode()) {first = res2;second = res1;}synchronized (first) {System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + first + " 锁成功");try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}synchronized(second) {System.out.println("线程:" + Thread.currentThread().getName() + "获取资源 " + second + " 锁成功");}}}).start();}}

打印结果如下

线程:Thread-0获取资源 java.lang.Object@7447157c 锁成功线程:Thread-0获取资源 java.lang.Object@7a80f45c 锁成功线程:Thread-1获取资源 java.lang.Object@7447157c 锁成功线程:Thread-1获取资源 java.lang.Object@7a80f45c 锁成功

给TA打赏
共{{data.count}}人
人已打赏
Java

什么是并发编程?

2020-7-31 0:18:20

Java

如何停止一个线程池?

2020-7-31 0:21:40

本站所发布的一切源码、模板、应用等文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权。本站内容适用于DMCA政策。若您的权利被侵害,请与我们联系处理,站长 QQ: 84087680 或 点击右侧 私信:盾给网 反馈,我们将尽快处理。
⚠️
本站所发布的一切源码、模板、应用等文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权。本站内容适用于DMCA政策
若您的权利被侵害,请与我们联系处理,站长 QQ: 84087680 或 点击右侧 私信:盾给网 反馈,我们将尽快处理。
0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索