线程安全问题(二)——死锁

news2024/11/23 3:48:19

死锁

  • 前言
  • 可重入锁
    • 逻辑
  • 两个线程两把锁(死锁)
  • 死锁的特点
  • 多个线程多把锁(哲学家就餐问题)
  • 总结


前言

在前面的文章中,介绍了锁的基本使用方式——锁

在上一篇文章中,通过synchronized关键字进行加锁操作,使得【多线程修改同一变量】的情况可以得到解决。
那么在本文中,将会继续讲解锁的相关知识点。


可重入锁

我们可以通过synchronized确定锁对象,对线程进行加锁的操作。那么如果锁对象重复使用是否会出现不一样的结果?
在下面的案例中,t1和t2线程使用了连续synchronized,设置的加锁对象同样是counter,如果运行这段代码,结果却是正确的。
理论上,当synchronized(counter)开始使用时,只有执行完其中的代码(大括号中的代码块)才会释放锁。而这个锁中又嵌套了相同的锁,按道理来说此时counter锁对象还没有被释放,应该出现阻塞等待状态最终代码无法运行才是。
那么为什么在Java中这段代码可以编译通过?
原因: 在Java中,已经对synchronized内部进行了特殊处理。在每个锁对象中,会记录当前是哪个线程持有这把锁,接下来,当针对这个对象进行加锁操作的时候就会进行判定,判定当前尝试加锁线程是否是这一对象锁的线程。 通过这样的操作,系统就可以知道如果不是,就会阻塞;如果是,就会直接放行。

class Counter {
    public static int count;
     void add(){
        count++;
    }
    public int getCount(){
        return count;
    }
}

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) {
                    synchronized (counter) {
                        counter.add();
                    }
                    
                }
            }
        });
        Thread t2 = new Thread(()-> {
            for (int i = 0; i < 50000; i++) {
                synchronized (counter) {
                    synchronized (counter) {
                        counter.add();
                    }
                }
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = "+counter.getCount());
    }

逻辑

当加了多层锁的时候,代码如何知道执行到哪里要真正进行解锁。如果有若干层加锁操作,如何判定当前遇到的}是最外层的} ?
以我的理解,进行加锁操作时在内部给锁对象设计了一个计数器(int n)。 每次遇到【{ 】时,n++,遇到【 } 】时,n–,当n=0时才真正解锁。
通过这样的方式,针对同一线程中的同一锁对象进行的锁操作,可以让程序猿避免了死锁的情况,我们也称之为可重入锁。

两个线程两把锁(死锁)

现在存在线程t1和线程t2;锁对象A和B。
当进行锁操作的时候,可能会出现这样的情况:线程1和线程2都需要使用到锁A和锁B,对于线程A来说,首先获取锁A然后获取锁B;而对于B来说,首先获取锁B再获取锁A。
让两个线程同时获得第一把锁,接下来需要尝试去获取对方的锁。

在下面的代码启动后,线程t1获取到了锁locker1,线程t2获取到了锁locker2.
对于t1,接下来需要获取locker2才能继续执行接下来的代码
对于t2,需要获取locker1才能继续执行接下来的代码

两个线程之间无法让步,于是一同进入了阻塞等待状态,都在等待对方释放锁。

public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();
        Thread t1 = new Thread(()-> {
            synchronized (locker1){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                }
                synchronized (locker2){
                    System.out.println("获取到了2把锁");
                }
            }
        });
        Thread t2 = new Thread(()-> {
            synchronized (locker2){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (locker1){
                    System.out.println("获取到了2把锁");
                }
            }
        });
        t1.start();
        t2.start();

    }

在执行这段代码后,我们可以通过jconsole查看线程状态。如下图所示,我们可以知道两个线程都处于阻塞状态,同时我们也可以知道阻塞所需要获取的锁目前在哪个线程身上。
在这里插入图片描述
在这里插入图片描述

死锁的特点

1.锁具有互斥性。这是锁的基本特点,当一个线程拿到锁A时,其他线程只能等待该线程释放。
2.锁不可抢占。只有该线程主动释放锁,别的线程无法抢占。
3.请求和保持。线程拿到锁以后可以继续尝试获取其他锁。
4.循环等待。多个线程多个锁的状态中,出现了A等待B,B等待A的情况。
当全部满足这些条件以后,就可以发生死锁。

多个线程多把锁(哲学家就餐问题)

情景:在一个餐桌上存在五个哲学家,他们做两件事情:一是思考哲学,二是就餐。
每个哲学家左右手各有一根筷子以供就餐时使用。如果哲学家手中只有一根筷子,他会等到另一根筷子拥有的时候才会就餐,而不会放下筷子。
在这里插入图片描述
通过这个情景,我们可以很明显的预知到一种情况:所有的哲学家手中都拿起左边的筷子,于是所有哲学家都停下了,进入阻塞等待状态。
我们通过这个问题可以反映到线程的情况,因为多线程多锁的原因,如果没有合理安排则会导致线程阻塞甚至死锁!

解决这种情况的一种办法就是约定好加锁的顺序,破除循环等待的情况。
在下面的代码中,两个线程轮流获取locker1和locker2,这就可以有效规避死锁的问题了。

    public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();
        Thread t1 = new Thread(()-> {
            synchronized (locker1){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (locker2){
                    System.out.println("获取到了2把锁");
                }
            }
        });
        Thread t2 = new Thread(()-> {
            synchronized (locker1){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (locker2){
                    System.out.println("获取到了2把锁");
                }
            }
        });
        t1.start();
        t2.start();

    }

总结

死锁在多线程中是及其常见的一个问题,导致了线程的不安全。是我们要极力规避的情况。我们了解了死锁发生的情况,死锁的原因等多个点。
本文使用源码☞ 源码

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

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

相关文章

只需10分钟1条,全是原创精美视频,拆分8个步骤详细讲解!

不少朋友在问如何快速学习剪辑视频&#xff0c;网上还有很多在收几百到几千学费。其实所有的付费&#xff0c;都是认知与信息差。 这篇文章我直接讲干货&#xff0c;内容不多&#xff0c;大概3分钟可以看完。所有步骤都是富哥亲测的内容&#xff0c;每条视频长达1分钟以上&…

检索增强生成RAG系列4--RAG优化之问题优化

在系列2的章节中罗列了对RAG准确度的几个重要关键点&#xff0c;主要包括2方面&#xff0c;这一章就针对其中问题优化来做详细的讲解以及其解决方案。 从系列2中&#xff0c;我们知道初始的问题可能对于查询结果不是很好&#xff0c;可能是因为问题表达模糊、语义与文档不一致等…

职场必备:三大神器助你完美驾驭工作与生活;从 GTD 到SMART再到OKR:提升效率的终极指南;告别拖延,高效工作的秘密武器!

在现代职场和个人生活中&#xff0c;有效的时间管理和目标设定是成功的关键。我们每天都面临着无数的任务和目标。如何在纷繁复杂的日常中保持专注&#xff0c;高效地完成工作&#xff1f; GTD&#xff08;Getting Things Done&#xff09; GTD&#xff08;Getting Things Don…

容器技术-docker4

一、docker资源限制 在使用 docker 运行容器时&#xff0c;一台主机上可能会运行几百个容器&#xff0c;这些容器虽然互相隔离&#xff0c;但是底层却使用着相同的 CPU、内存和磁盘资源。如果不对容器使用的资源进行限制&#xff0c;那么容器之间会互相影响&#xff0c;小的来说…

Qt:5.QWidget属性介绍(isEnabled和geometry)

目录 一、 QWidget属性的介绍&#xff1a; 二、Enabled属性&#xff1a; 2.1Enabled属性的介绍&#xff1a; 2.2获取控件当前可用状态的api——isEnabled()&#xff1a; 2.3设置控件当前的可用状态的api—— setEnabled() &#xff1a; 2.4 实例&#xff1a;通过一个按钮&…

Gin框架基础

1、一个简单的Gin示例 下载并安装Gin: go get -u github.com/gin-gonic/gin1.1 一个简单的例子 package mainimport ("net/http""github.com/gin-gonic/gin" )func main() {// 创建一个默认的路由引擎r : gin.Default()// 当客户端以GET方式访问 /hello…

企业化运维(6)_redis数据库

Redis&#xff08;Remote Dictionary Server )&#xff0c;即远程字典服务&#xff0c;是一个开源的使用ANSIC语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 redis是一个key-value存储系统。和Memcached类似&#xff0…

优化模型验证30:多车场车辆路径问题模型及Gurobipy验证

目录 1 数学模型 1.1 用到的符号集合 1.2 模型公式 2 模型验证代码 2.1 Gurobipy代码 2.2 结果可视化 多车场车辆路径问题的定义:大型的物流公司拥有多个车场,而每个车场都有若干车辆用于配送,决策者需要根据客户的所在位置,将客户分配到合适的车场和车辆中。 1 数学模…

深度学习基准模型Transformer

深度学习基准模型Transformer 深度学习基准模型Transformer&#xff0c;最初由Vaswani等人在2017年的论文《Attention is All You Need》中提出&#xff0c;是自然语言处理&#xff08;NLP&#xff09;领域的一个里程碑式模型。它在许多序列到序列&#xff08;seq2seq&#xf…

matlab仿真 通信信号和系统分析(上)

&#xff08;内容源自详解MATLAB&#xff0f;SIMULINK 通信系统建模与仿真 刘学勇编著第三章内容&#xff0c;有兴趣的读者请阅读原书&#xff09; 一、求离散信号卷积和 主要还是使用卷积函数conv&#xff0c;值得注意的是&#xff0c;得到的卷积和长度结果为81&#xff0…

lumbda常用操作

文章目录 lumbda的常用操作将List<String>转List<Integer>filter 过滤max 和min将List<Object>转为Map将List<Object>转为Map&#xff08;重复key&#xff09;将List<Object>转为Map&#xff08;指定Map类型&#xff09;过滤List重复 lumbda的常…

【强化学习的数学原理】课程笔记--2(贝尔曼最优公式,值迭代与策略迭代)

目录 贝尔曼最优公式最优 Policy求解贝尔曼最优公式求解最大 State Value v ∗ v^* v∗根据 v ∗ v^* v∗ 求解贪婪形式的最佳 Policy π ∗ \pi^* π∗一些证明过程 一些影响 π ∗ \pi^* π∗ 的因素如何让 π ∗ \pi^* π∗ 不 “绕弯路” γ \gamma γ 的影响reward 的…

15- 22题聚合函数 - 高频 SQL 50 题基础版

目录 1. 相关知识点2. 例子2.15 - 有趣的电影2.16 - 平均售价2.17 - 项目员工 I2.18 - 各赛事的用户注册率2.19 - 查询结果的质量和占比2.20 - 每月交易 I2.21 - 即时食物配送 II2.22 - 游戏玩法分析 IV 1. 相关知识点 函数 函数含义order by排序group by分组between 小值 an…

基于web的产品管理系统

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于web的产品管理系统,java项目。 ecli…

2024最新boss直聘岗位数据爬虫,并进行可视化分析

前言 近年来,随着互联网的发展和就业市场的变化,数据科学与爬虫技术在招聘信息分析中的应用变得越来越重要。通过对招聘信息的爬取和可视化分析,我们可以更好地了解当前的就业市场动态、职位需求和薪资水平,从而为求职者和招聘企业提供有价值的数据支持。本文将介绍如何使…

Chrome浏览器web调试(js调试、css调试、篡改前置)

目录 1. 打开开发者工具(Dev Tool) 2. 打开命令菜单 截图 3. 面板介绍 4. CSS调试 右键检查快速到达元素处 查找DOM数 利用面板Console查找DOM节点 内置函数查找上一个选择点击的元素 5. 调试JS代码(Javascript调试) 日志调试 选择查看日志等级 眼睛观测变量 …

关于Unity运行时动态修改材质的小秘密

一、问题背景 在以往的Unity项目中涉及到修改材质的需求时&#xff0c;也只是改改材质贴图&#xff0c;材质颜色等&#xff0c;也没遇到那么多动态修改材质的坑。最近在做Unity App Demo时也遇到了要修改材质的小需求&#xff0c;本以为几分钟就能完成了&#xff0c;却花费了我…

【FPGA项目】System Generator算法板级验证-快速搭建外围测试电路

&#x1f389;欢迎来到FPGA专栏~System Generator算法板级验证-快速搭建外围测试电路 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;FPGA学习之旅 文章作者技术和水平有限&#xff0c;如果文…

深入解析MySQL语句的执行步骤

目录 MySQL架构概述语句执行步骤总览连接管理与线程处理语法解析查询缓存语义解析与预处理查询优化执行计划生成存储引擎层执行结果集返回优化查询性能的技巧结论 MySQL架构概述 在深入探讨MySQL语句执行的具体步骤之前&#xff0c;我们先来了解MySQL的整体架构。MySQL架构主…

简单多状态DP问题

这里写目录标题 什么是多状态DP解决多状态DP问题应该怎么做&#xff1f;关于多状态DP问题的几道题1.按摩师2.打家劫舍Ⅱ3.删除并获得点数4.粉刷房子5.买卖股票的最佳时期含手冷冻期 总结 什么是多状态DP 多状态动态规划&#xff08;Multi-State Dynamic Programming, Multi-St…