• Java多线程——死锁的成因与解决


    目录

    1.死锁的概念

    2.死锁的成因

    3.如何避免死锁


    1.死锁的概念

    死锁是指两个或者两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造的一种阻塞的现象,若无外力作用,他们都将无法推进下去。此时称系统处于死锁状态或者系统产生了死锁,这些永远互相等待的进程称为死锁进程。(当然线程也是如此)比如下面的这个表情包就很好地解释了死锁的一种情况。

     

     或者我们也可以用另外一个例子来帮助大家理解死锁:

    张三和女神一起去饺子馆吃饺子. 吃饺子需要酱油和醋.

    张三抄起了酱油瓶, 女神抄起了醋瓶.

    张三: 你先把醋瓶给我, 我用完了就把酱油瓶给你.

    女神: 你先把酱油瓶给我, 我用完了就把醋瓶给你.

    如果这俩人彼此之间互不相让, 就构成了死锁.

    酱油和醋相当于是两把锁, 这两个人就是两个线程

    2.死锁的成因 

     这里大家可以自行去了解一下哲学家就餐问题_百度百科,因为问题比较麻烦,所以不花过多篇幅去讲解了,我们直接抛出结论。

    死锁产生的四个必要条件:

    互斥使用:即当资源被一个线程使用(占有)时,别的线程不能使用

    不可抢占:资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

    请求和保持:即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

    循环等待:即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样 就形成了一个等待环路。

    当上述四个条件都成立的时候,便形成死锁。 

    3.死锁的解决 

    当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。 其中最容易破坏的就是 "循环等待"。

    破坏循环等待

    最常用的一种死锁阻止技术就是锁排序. 假设有 N 个线程尝试获取 M 把锁, 就可以针对 M 把锁进行编号 (1, 2, 3...M). N 个线程尝试获取锁的时候, 都按照固定的按编号由小到大顺序来获取锁. 这样就可以避免环路等待.

    可能产生环路等待的代码: 

    1. Object lock1 = new Object();
    2. Object lock2 = new Object();
    3. Thread t1 = new Thread() {
    4. @Override
    5. public void run() {
    6. synchronized (lock1) {
    7. synchronized (lock2) {
    8. // do something...
    9. }
    10. }
    11. }
    12. };
    13. t1.start();
    14. Thread t2 = new Thread() {
    15. @Override
    16. public void run() {
    17. synchronized (lock2) {
    18. synchronized (lock1) {
    19. // do something...
    20. }
    21. }
    22. }
    23. };
    24. t2.start();

    两个线程对于加锁的顺序没有约定,容易产生环路等待。(注意不是一定产生死锁,只是大概率)

    不会产生环路等待的代码: 

    1. Object lock1 = new Object();
    2. Object lock2 = new Object();
    3. Thread t1 = new Thread() {
    4. @Override
    5. public void run() {
    6. synchronized (lock1) {
    7. synchronized (lock2) {
    8. // do something...
    9. }
    10. }
    11. }
    12. };
    13. t1.start();
    14. Thread t2 = new Thread() {
    15. @Override
    16. public void run() {
    17. synchronized (lock1) {
    18. synchronized (lock2) {
    19. // do something...
    20. }
    21. }
    22. }
    23. };
    24. t2.start();

    约定好先获取 lock1, 再获取 lock2 , 就不会环路等待。

    当然预防出现死锁的方法不止一种,我们只是讲了一种比较简单易懂的方法。大家感兴趣的还可以去学习一下银行家算法,它同样可以避免死锁,但比较复杂我们不再展开。

  • 相关阅读:
    mysql—多表查询
    linux 常用命令笔记
    Flutter 项目实战 实现上传头像和个人资料 (五)
    “行泊一体”的火爆与现实困境
    软件项目管理课后习题——第8章软件项目的人员与沟通管理
    模型优化整体方向概述
    49数码论坛系统设计与实现
    Apache hudi 核心功能点分析
    基础会计学名词解释
    git rebase 使用详解
  • 原文地址:https://blog.csdn.net/weixin_60778429/article/details/126123371