[线程]线程不安全问题 --- 死锁

news2024/9/27 21:29:43

文章目录

  • 一. 引出死锁
  • 二. 可重用锁
  • 三. 死锁的三种典型场景
  • 四. 死锁产生的四个必要条件(面试题)
    • 1. 锁具有互斥特性
    • 2. 锁不可抢占(不可被剥夺)
    • 3. 请求和保持
    • 4. 循环等待
  • 五. 避免死锁问题

一. 引出死锁

class Counter{
    private int count;
    public void add(){
        synchronized(this){
            count++;
        }
    }
    public int get(){
        return count;
    }
}
public class Demo15 {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(() -> {
            for(int i = 0; i < 50000; i++){
                synchronized(counter){
                    counter.add();
                }
            }
        });
        t1.start();
        t1.join();
        System.out.println("count = " + counter.get());
    }
}

上述代码, 我们在counter对象调用add时加了counter锁, 同时在add方法中也加counter锁, 这时我们就对同一块代码加了两层锁, 形成了锁嵌套锁的结构
在这里插入图片描述

思考:
当我们第一次对count上锁时, 是肯定会成功的
当第二次尝试加count锁时, 此时这个锁已经是被锁住的状态
按照之前的理解, 对一个已经被锁住的对象再进行加锁时, 就会出现阻塞等待
等待count锁被释放, 才能再进行加锁
但是,
要想获取到第二层锁, 就需要执行完第一层锁的大括号
要想执行完第一层锁的大括号, 就需要先获取第二层锁
这种现象就叫死锁

二. 可重用锁

运行上述代码:
在这里插入图片描述
发现并没有发生问题, 原因在于:
synchronized这个关键字, JVM在内部进行了特殊的处理
每个锁对象, 都会记录下来当前是哪个线程持有了这个锁,
当针对一个对象加锁操作时, 先会判定一下, 当前尝试加锁的线程, 是否是持有这个锁的状态,
如果没有持有这个锁, 则需要等待其他线程解锁
如果持有这个锁, 则直接放行, 就会加一遍相同的锁!!
这样的机制, 叫做==“可重用锁”==, 目的就是为了避免程序员搞出死锁

注意: 这是java锁synchronized特殊的地方, 如果是c++ / Python的锁, 嵌套锁就会发生死锁!!

三. 死锁的三种典型场景

场景一: 一个线程针对一个对象, 连续加锁(不可重入锁)两次
就是上述的问题:
如果是不可重入锁, 并且一个线程针对一个对象, 连续加锁两次, 就会引起死锁
解决方法就是引入不可重入锁

场景二: 两个线程两把锁
现在又线程1和线程2, 有两把锁A和B
两个线程先分别获取两把锁, 线程1获取A, 线程2获取B, 分别拿到锁后, 在释放之前, 再次尝试获取对方的锁

public class Demo16 {
    public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (locker1){
                try {
                    Thread.sleep(1000);//让t1等待一下t2启动, 获取locker2
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (locker2){
                    System.out.println("t1获取了两把锁");
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (locker2){
                synchronized (locker1){
                    System.out.println("t2获取了两把锁");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

此时运行发现:
在这里插入图片描述
代码正在运行, 但是什么也没打印, 说明t1t2都没拿到两把锁
t1等待t2释放locker2, t2等待t1释放locker1, 就发生了死锁

此时可以通过jconsole工具观察线程状态:
t1:
在这里插入图片描述
t2:
在这里插入图片描述
都是BLOCKED状态
场景三: N个线程, M把锁
随着线程数目 / 锁的个数增加, 此时情况就更复杂了, 就更容易出现死锁了

一个经典问题: 哲学家进餐问题
一张圆桌上面一碗面条, 假设共5个哲学家, 每个哲学家之间都有一根筷子, 哲学家可以选择:
1)思考人生(放下筷子)
2)吃面条, (此时需要拿起左右两根筷子, 才能吃面条)
在这里插入图片描述

每个哲学家啥时候吃面条, 啥时候思考人生, 这都是不确定的, 模拟线程抢占式执行
大多数情况下, 都是可以正常运行的, 只需要等待即可
但是如果出现极端情况, 就会出现问题:
如果同一时刻, 所有的哲学家都想要吃面条, 都同时拿起了左边的筷子, 那么当想要拿起右边筷子的时候, 就会发生阻塞等待, 每一个人都在等待右边的人放下筷子, 此时就会发生死锁!

四. 死锁产生的四个必要条件(面试题)

死锁, 是一个非常严重的问题!!
死锁的发生就会导致线程被卡住, 没法继续执行
同时, 死锁的产生是随机性的, 可能测试一万次都没有发生, 但是无法保证第一万零一次是否会发生死锁

死锁产生的四个必要条件:
(必要条件:缺一不可, 任何一个死锁场景,都必须具备以下四点!!!)

1. 锁具有互斥特性

这是锁的基本特点, 一个线程拿到锁后, 其他线程就得阻塞等待
(锁的基本特点)

2. 锁不可抢占(不可被剥夺)

一个线程拿到锁后, 除非自己主动释放, 别人是没法抢占的
(锁的基本特点)

3. 请求和保持

一个线程拿到一把锁后, 在不释放这个锁的前提下, 再尝试获取其他锁
(代码结构)

4. 循环等待

多个线程获取多个锁的过程中, 出现了循环等待, 如A等B, B等A
(代码结构)

五. 避免死锁问题

根据上面死锁产生的条件, 分析如何避免死锁
条件一和条件二使我们无法改变的
条件三我们可以尽量避免不让锁嵌套获取, 但是有的时候为了线程安全, 我们又必须要嵌套
条件四, 我们可以破除循环等待, 技术出现嵌套, 也不会死锁
那么我们就可以通过约定加锁顺序来避免死锁
例如上述代码:
我们约定好, 只能先加locker1, 再加locker2
在这里插入图片描述
这样就不会出现死锁了, t2只能等待t1释放locker1后, 才能继续执行在这里插入图片描述

再看哲学家就餐问题, 如果我们给每根筷子编号, 当哲学家拿筷子的时候, 只能拿编号小的筷子, 此时, 当5个人同时拿起筷子:

在这里插入图片描述
当代码中, 确实需要用到多个线程获取多把锁, 一定要约定好加锁顺序, 就可以有效避免死锁了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2089278.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习语义分割篇——DeeplabV3原理详解+源码实战

&#x1f34a;作者简介&#xff1a;秃头小苏&#xff0c;致力于用最通俗的语言描述问题 &#x1f34a;专栏推荐&#xff1a;深度学习网络原理与实战 &#x1f34a;近期目标&#xff1a;写好专栏的每一篇文章 &#x1f34a;支持小苏&#xff1a;点赞&#x1f44d;&#x1f3fc;、…

域内安全:委派攻击

目录 域委派 非約束性委派攻击&#xff1a; 主动访问&#xff1a; 被动访问&#xff08;利用打印机漏洞&#xff09; 约束性委派攻击&#xff1a; 域委派 域委派是指将域内用户的权限委派给服务账户&#xff0c;使得服务账号能够以用户的权限在域内展开活动。 委派是域中…

机器学习——决策树模型

决策树原理 算法概述 从根节点开始一步步走到叶子节点&#xff08;决策&#xff09; 所有数据最终都会落到叶子节点&#xff0c;既可以做分类也可以做回归 例如上例&#xff0c;输入一个数据后&#xff0c;先判断他的年龄&#xff0c;然后再判断性别 在决策树中&#xff0c…

day45.动态规划

1035.不相交的线: 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#xff0c;这些直线需要同时满足&#xff1a; nums1[i] nums2[j] 且绘制的直线不与任何其他连线&#xff08;非水…

基站定位系统的创新应用:企业管理的新利器

在现代企业的管理中&#xff0c;基站定位系统已经成为不可或缺的技术手段。通过这一系统&#xff0c;企业能够实时掌握物资、人员的位置和状态&#xff0c;提升管理效率和安全性。常达智能物联凭借深厚的技术积累和丰富的项目经验&#xff0c;为各类企业提供了创新的基站定位系…

如何使用ChatGPT,提示词篇之【编程代码】

一、 ChatGPT可以做什么&#xff1f; ChatGPT能做的事情非常多&#xff01;它不仅仅是一个对话AI。以下是一些主要功能&#xff1a; 1. 回答问题&#xff1a;无论是学术问题、技术问题&#xff0c;还是生活琐事&#xff0c;ChatGPT都能提供帮助。 2. 写作助手&#xff1a;可以…

Angular17(3):Angular项目中引入iconfont

在Angular项目中引入Iconfont&#xff08;图标字体&#xff09;是一个常见的需求&#xff0c;用于在应用中添加丰富的图标资源。 Iconfont-阿里巴巴矢量图标库 1、点击进入官网&#xff0c;注册并登录 2、登陆成功后&#xff0c;首页的 资源管理 > 我的项目 点击进入 3、…

网络通信---四次挥手

文章目录 概述四次挥手第一次挥手&#xff1a;第二次挥手&#xff1a;第三次挥手&#xff1a;第四次挥手&#xff1a; 问题&#xff1a;为什么是四次&#xff0c;而不是三次&#xff1f;确保数据传输完成&#xff1a;防止数据丢失&#xff1a;避免旧连接干扰&#xff1a;防止死…

C#的继承

继承是面向对象程序设计中最重要的概念之一.继承允许我们根据一个类来定义另一个类,这使得创建和维护应用程序变得更容易,同时,也有利用重用代码和节省开发时间. 当创建一个类时,程序员不需要完全重新编写新的数据成员和成员函数只需要设计一个新的类,继承了已有的类的成员即可…

斯坦福UE4 C++课学习补充23:AI自定义任务

文章目录 一、自定义任务节点二、优化1. 子弹发射冷却2. 攻击时面朝玩家 一、自定义任务节点 本节需要创建自定义任务节点BTTask&#xff0c;实现小兵进入角色范围后进射击的功能。对于BTTaskNode的子类&#xff0c;我们需要在代码中重写ExecuteTask函数即可。返回值为EBTNode…

博客自建(带避坑指南)4:hexo文章页设置和动画魔改设置

咕咕了好久&#xff0c;这次终于来更新一下 看完上一篇博客&#xff1a; 博客自建&#xff08;带避坑指南&#xff09;3&#xff1a;简单的hexo网页界面设置-CSDN博客 想必你已经完成了头像、图片等一些基础的设置&#xff0c;但是这些改动都是比较基础的&#xff0c;现在我们…

【Java】MyBatis Plus 自动生成代码相关配置 (图解)

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;三、问题描述四、解决方案&#xff1a;4.1 认识依赖4.1.2 模板依赖4.1.2 代码生成相关依赖 4.2 自动生成代码4.2.1 认识…

钉钉-即时通讯-工作通知

钉钉-即时通讯-工作通知 钉钉官方文档创建以及获取应用配置代码创建工作通知工具类创建钉钉消息实体类好了接下来就可以直接使用了 钉钉官方文档 https://open.dingtalk.com/document/orgapp/asynchronous-sending-of-enterprise-session-messages 创建以及获取应用配置 1.进入…

最大池化、非线性激活、线性层

一、最大池化原理 二、最大池化实例 import torch import torchvision from torch import nn from torch.nn import MaxPool2d from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriterdataset torchvision.datasets.CIFAR10("../c…

springboot+vue+mybatis计算机房屋服务平台+PPT+论文+讲解+售后

近些年来&#xff0c;随着科技的飞速发展&#xff0c;互联网的普及逐渐延伸到各行各业中&#xff0c;给人们生活带来了十分的便利&#xff0c;房屋中介服务平台利用计算机网络实现信息化管理&#xff0c;使整个房屋中介服务的发展和服务水平有显著提升。 本文拟采用Eclipse开发…

Datawhale X 李宏毅苹果书 AI夏令营第五期 DL进阶方向 Task2笔记

Datawhale X 李宏毅苹果书 向李宏毅学深度学习&#xff08;进阶&#xff09; 是 Datawhale 2024 年 AI 夏令营第五期的学习活动&#xff08;“深度学习 进阶”方向&#xff09; 往期task1链接&#xff1a;深度学习进阶-Task1 我做的task1的笔记博客&#xff1a;传送门 Datawhal…

Tensorflow实现深度学习8:猫狗识别

本文为为&#x1f517;365天深度学习训练营内部文章 原作者&#xff1a;K同学啊 一 导入数据 import matplotlib.pyplot as plt import tensorflow as tf # 支持中文 plt.rcParams[font.sans-serif] [SimHei] # 用来正常显示中文标签 plt.rcParams[axes.unicode_minus] Fals…

开放式耳机会漏音吗?开放式耳机测评

开放式耳机由于其独特的设计&#xff0c;允许声音在一定程度上自然地与外界环境融合。这种设计带来的一个常见误解是&#xff0c;人们可能会认为开放式耳机会有较大的声音泄露。然而&#xff0c;实际上&#xff0c;高质量的开放式耳机通过精心的声学设计&#xff0c;可以有效地…

Video Recording,视频录制

一.录屏软件 1.1 Xbox Game Bar 对于 win 来说&#xff0c;快捷键是 Win G&#xff0c;即可以启动 Xbot Game Bar 来进行录制。但是有一个比较致命的缺点就是&#xff0c;当我们切换页面的时候&#xff0c;录制就会失败&#xff0c;这款还是很适合于单页面的视频录制。 1.2 …

Python以及Python历史版本的安装的安装

文章目录 前言Python的安装Python历史版本下载总结 前言 Python 是一种广泛使用的高级编程语言&#xff0c;以其简洁易读的语法和强大的功能而受到开发者的青睐。从数据分析到网页开发&#xff0c;从自动化脚本到人工智能&#xff0c;Python 的应用领域几乎无处不在。然而&…