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

死锁

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


前言

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

在上一篇文章中,通过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.mfbz.cn/a/761170.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在Stimulsoft 报告中连接来自 MySQL 的数据

Stimulsoft Ultimate &#xff08;原Stimulsoft Reports.Ultimate&#xff09;是用于创建报表和仪表板的通用工具集。该产品包括用于WinForms、ASP.NET、.NET Core、JavaScript、WPF、PHP、Java和其他环境的完整工具集。无需比较产品功能&#xff0c;Stimulsoft Ultimate包含了…

数据结构(Java):迭代器遍历【底层源码解析】

1、引言 我们知道&#xff0c;对于List系列集合&#xff0c;添加的元素是有序、可重复、有索引的&#xff1b;而对于Set系列集合&#xff0c;添加的元素是无序、不重复、无索引的。 那么使用for循环通过下标来对Set系列集合进行遍历&#xff0c;那显然是不行的。 迭代器就可…

docker k8s

1、docker是什么&#xff1f; 将环境和程序一起打包给到 服务器运行的工具软件。 2、基础镜像base image是什么&#xff1f; 操作系统&#xff1a;用户空间、内核空间 阉割操作系统&#xff0c;利用其的用户空间&#xff08;因为应用程序运行在用户空间&#xff09;&#xf…

转换Python2 转 Python3 小程序

转换Python2 -> Python3 藉由python35\Tools\scripts\2to3.py 档转换 python D:\python\Tools\scripts\2to3.py D:\Users\a0979\Desktop\scrip.pypython [转档程式] [欲转档.py] * python 为 python3 ->因为转档程式为python3 ![](https://i.imgur.com/PeQkvhk.png)py…

STM32的EXTI简介

一&#xff0c;EXTI&#xff08;External Interrupt&#xff09;外部中断事件控制器 什么是EXTI&#xff1f; 1.监测指定的GPIO口的电平信号变化&#xff0c;并检测到指定条件时&#xff0c;向内核的中断控制器NVIC发出中断申请。NVIC在裁决后&#xff0c;如果满足条件&#xf…

工业4.0能给电能表带来什么机会

一、技术革新与升级 工业4.0的核心在于智能化和网络化&#xff0c;这促使电能表行业进行技术革新和升级。传统的电能表功能单一&#xff0c;主要用于测量电能消耗。而在工业4.0的推动下&#xff0c;电能表逐渐发展成为集信息储存和处理、网络通信、实时监测等多种功能于一体的…

云计算【第一阶段(22)】Linux的进程和计划任务管理

目录 一、查看进程 1.1、程序和进程的关系 1.2、查看进程 1.2.1、静态查看进程信息ps ​编辑 1.2.1.1、实验 1.2.2、动态查看进程信息top 1.2.2.1、实验 1.2.2.2、top 命令全屏操作界面快捷键 1.2.3、pgrep根据特定条件查询进程pid信息 1.2.4、pstree命令以树形结构列出…

5、Python之rich:GUI之外,终端呈现也能玩出花

引言 在Python系列文章的上一篇中&#xff0c;我们从print的定义出发&#xff0c;进一步探索了print()函数更多的用法&#xff0c;尤其是一些哪怕是Python老手也可能忽略的用法。没有阅读的或者需要回顾print()及输出格式化的扩展用法&#xff0c;可以查看上一篇文章。 虽然pr…

Git安装与使用及整合IDEA使用的详细教程

1. 版本控制软件介绍 版本控制软件提供完备的版本管理功能&#xff0c;用于存储、追踪目录&#xff08;文件夹&#xff09;和文件的修改历史&#xff0c;是软件开发者的必备工具&#xff0c;是软件公司的基础设施。版本控制软件的最高目标&#xff0c;是支持软件公司的配置管理…

Swift 新结构化并发中鲜为人知的 isolated 参数

概述 伴随着 Swift 5.5&#xff08;WWDC21&#xff09;推出的新结构化并发到今年的 WWDC 24 已经有 3 个多年头了。想必大家都对其中 async/awiat、async let、TaskGroup、Actor 等各种概念都了然于胸了吧&#xff1f; 不过小伙伴们可能不知道的是&#xff1a;新结构化并发&a…

【Eplan】P8-图纸设计的四种方法

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 了解 EPLAN 的图纸设计的四种方法&#xff1a;面向图形、面向设备、面向物料清单、面向安装情况&#xff1b; 2、 问题场景 为什么 EPLAN 要使用四种不同的设计方法。 3、软硬件环境 1、软件版本&#xff1a;EPLAN…

Ubuntu无法安全地用该源进行更新,所以默认禁用该源。

解决方案 1. 获取并添加缺失的 GPG 公钥 可以使用 apt-key 命令来添加缺失的公钥。根据错误信息&#xff0c;缺失的公钥是 3B4FE6ACC0B21F32。 sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F322. 更新软件包列表 添加公钥后&#xff0c;更…

ADS131A04硬件设计与软件调试

一、IC基本信息 ADS131A0x 双通道或四通道 24 位 128kSPS 同步采样 Δ-Σ ADC •双通道或四通道同步采样差分输入 • 数据速率&#xff1a;高达 128kSPS • 高性能&#xff1a; – 单通道精度&#xff1a;在 10,000:1 动态范围内优于 0.1% – 有效分辨率&#xff1a;20.6位…

【MotionCap】搭建wsl2的pytorch环境

参考大神:wsl2-ubuntu版本 cuda下周cuda11.3 wget https://developer.download.nvidia.com/compute/cuda/11.3.0/local_installers/cuda_11.3.0_465.19.01_linux.run sudo sh cuda_11.3.0_465.19.01_linux.run cuda是开源的么?下15分钟

重磅!云起无垠荣获“智能模糊测试赛道领航者”等多项殊荣

近日&#xff0c;以 “新质•真能力”&#xff08;新质生产力&#xff0c;安全真能力&#xff09;为主题的第四届数字安全大会正式召开。在此次大会上&#xff0c;数世咨询发布了《中国数字安全产业年度报告(2024)》、新质百强榜单以及国内首本《数字安全蓝皮书》。这些报告和榜…

操作系统精选题(四)(论述题)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;操作系统 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 前言 一、银行家算法的一道例题 二、页…

【NOI】C++程序设计入门四

文章目录 前言一、浮点型&#xff08;float和double&#xff09;1.float类型2.double类型 二、保留小数的方法方法一&#xff1a;方法二&#xff1a; 三、样题讲解问题1&#xff1a;1603. 冷饮的价格&#xff1f;问题2&#xff1a;1957. 求三个数的平均数问题3&#xff1a;1602…

爬数据是什么意思?

爬数据的意思是&#xff1a;通过网络爬虫程序来获取需要的网站上的内容信息&#xff0c;比如文字、视频、图片等数据。网络爬虫&#xff08;网页蜘蛛&#xff09;是一种按照一定的规则&#xff0c;自动的抓取万维网信息的程序或者脚本。 学习一些爬数据的知识有什么用呢&#x…

(PC+WAP)高端大气的装修装潢公司网站模板

(PCWAP)高端大气的装修装潢公司网站模板PbootCMS内核开发的网站模板&#xff0c;该模板适用于装修公司网站、装潢公司网站等企业&#xff0c;当然其他行业也可以做&#xff0c;只需要把文字图片换成其他行业的即可&#xff1b;(PCWAP)&#xff0c;同一个后台&#xff0c;数据即…

Vue2动态代理,换服务无须重启项目

1、痛点 当我们需要使用不同的服务器时&#xff0c;就需要手动修改vue.config.js中配置并重新启动项目。当项目越来越大时&#xff0c;会需要较长的时间来等待项目启动&#xff0c;如此反复&#xff0c;极大影响我们开发进度。 2、寻求解决方案 vue-cli 的代理是使用的http-p…