线程安全(二)--死锁

news2024/11/27 10:45:59

@TOC

一:什么是死锁???

public class Demo1 {
    public static void main(String[] args) {
        Object locker=new Object();

        Thread thread=new Thread(()->{
            synchronized(locker){
                synchronized (locker){
                    System.out.println("hello thread");
                }
            }
        });
        thread.start();
    }
}

上述代码:thread一共加了两次锁,第一次加锁,肯定是能够成功的,但当第二次加锁的时候,此时,第一次加锁后,还未进行解锁操作,那么第二次加锁操作就不能获得锁对象,就会加锁失败,那么该线程就会进入阻塞等待,等待到第一次加锁 操作完了,释放锁操作,但第一次的操作要释放锁,那么必须执行完第二次加锁,解锁操作(代码顺序执行).
这样就会造成第二次加锁操作阻塞等待,等第一次操作释放锁,由于代码顺序执行,第一次要想释放锁,那必须执行完第二次的synchronized操作,那么就非常矛盾,这种情况,就叫做死锁.

二:可重入锁

当我们运行程序的时候,发现代码正常执行了,并没有进入死锁状态.
这是为什么???
是因为synchronized,对上述情况做出处理(JVM).
每个锁对象里,会记录当前是哪个线程持有了这个锁,当针对这个锁对象进行加锁操作的时候,就会先判定一下,当前尝试加锁的线程,是否是持有锁的线程,如果是,直接进行加锁操作,如果不是,那就阻塞等待.这种锁也就作可重入锁

synchronized(locker){
                synchronized (locker){
                    System.out.println("hello thread");
                }//1
            }//2

上面代码,当加了2层锁的时候,代码执行到哪里是要真正的进行解锁操作呢?
肯定是在2这里解锁(最外层的}),否则,如果在1解锁,那么1和2中间有代码的话,是没有被保护起来的.
同理:如果加了N层锁,是如何判定遇到的**}是最外层的呢?
其实,JVM会给锁对象维护一个计数器(int n),每次遇到
{** 就加1,(只有第一次才是真正的加锁),每次遇到**}**,n就-1,当n=0的时候,就是真正的解锁.

三:死锁的典型场景:

1:场景一:不可重入锁

锁是不可重入锁,并且一下线程针对一个锁对象,连续加锁多次,
引入可重入锁,问题就解决了.

2:场景二:两个线程,两把锁

有线程1和线程2,锁A和锁B;现在,线程1拿到了锁A,线程2拿到了锁B,然后线程1想要获取锁B,就需要阻塞等待,等待线程2 释放锁B,同理线程2想要获取到锁A,就需要等待线程1释放锁A,两个线程都进入阻塞等待,那么就会引起死锁问题.

public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Object locker1=new Object();
        Object locker2=new Object();
        Thread t1=new Thread(()->{
            synchronized(locker1){
                try {
                    Thread.sleep(1000);//sleep()是为了让t2线程拿到另一把锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2){
                    System.out.println("hello t1");
                }
            }
        });
        Thread t2=new Thread(()->{
            synchronized(locker2){
                synchronized (locker1){
                    System.out.println("hello t2");
                }
            }
        });
        t1.start();

        t2.start();
    }
}

此时:t1 尝试对locker2加锁,就会阻塞等待,等待t2释放locker2;t2尝试对locker1加锁,也会阻塞等待,等待t1释放locker1.
在这里插入图片描述
在这里插入图片描述
代码执行结果:进程没有退出,也没有打印任何的内容,死锁了.
通过jconslole可以看到这里线程的状态:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3:场景三:N个线程,M把锁

哲学家就餐问题:
每个滑稽都坐在两个筷子之间.每个滑稽要做两件事:
1:思考人生(放下筷子),
2:吃面条(拿起左右两根筷子)
每个哲学家什么时候吃面条,什么时候思考人生,都是不确定的(线程抢占式执行)
那么就会出现极端情况,同一时刻,所以的滑稽都拿起左边的筷子,此时,所以的滑稽都无法拿起右边的筷子,并且每个滑稽都是固执的人(每个哲学家只有吃不到面条,绝不会放下手中的筷子),那么就会引起死锁问题

四:如何解决死锁问题???

死锁,是非常严重的问题,就会使线程阻塞等待,使线程卡住了,没法正常工作了,更可怕的是,死锁这种bug,往往都是概率性出现.
那么就必须解决死锁问题,同时线程安全问题也必须解决.

4.1:死锁的4个必要条件

1:锁具有互斥特性(锁的基本特点,一个线程拿到锁之后,其他线程要想拿到这个锁,就必须阻塞等待).
2:锁不可抢占(不可被剥夺):一个线程拿到锁之后,除非它自己主动释放锁,否则其他线程抢不走(锁的基本特点).
3:请求和保持(嵌套锁):一个线程拿到一把锁之后,不释放这个锁的前提下,再尝试获取别的锁.
4:循环等待:多个线程获取多个锁的过程中,出现了循环等待.A线程等待B,B线程等待A.
若要构成死锁,这4个条件缺一不可,那么如果要解决死锁问题,就要从这四个条件下手.而条件1和条件2,是锁的基本特性,无法修改.

4.2:从条件3解决死锁问题:

public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Object locker1=new Object();
        Object locker2=new Object();
        Thread t1=new Thread(()->{
            synchronized(locker1){
                try {
                    Thread.sleep(1000);//sleep()是为了让t2线程拿到另一把锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            synchronized (locker2){
                System.out.println("hello t1");
            }
        });
        Thread t2=new Thread(()->{
            synchronized(locker2){

            }
            synchronized (locker1){
                System.out.println("hello t2");
            }
        });
        t1.start();

        t2.start();
    }
}

在这里插入图片描述
但有的情况下,嵌套锁的情况必须存在,那么只好用另一种方法了.

4.3:从条件4解决死锁问题:

public class Demo3 {
    public static void main(String[] args) {
        Object locker1=new Object();
        Object locker2=new Object();
        Thread t1=new Thread(()->{
            synchronized (locker1){
                synchronized (locker2){
                    System.out.println(" t1获得了两把锁");
                }
            }

        });
        Thread t2=new Thread(()->{
            synchronized (locker1){
                synchronized (locker2){
                    System.out.println(" t2获得了两把锁");
                }
            }

        });
        t1.start();
        t2.start();

    }
}

在这里插入图片描述

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

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

相关文章

Facebook Horizon Worlds:打造数字世界的社交乐园

在数字化时代,社交媒体平台已经成为人们日常生活中不可或缺的一部分。而随着科技的不断发展,人们对于社交体验的需求也在不断演变。在这样的背景下,Facebook推出了全新的虚拟现实社交平台——Facebook Horizon Worlds,旨在打造一个…

3D数据格式导出工具HOOPS Publish如何生成高质量3D PDF?

在当今数字化时代,从建筑设计到制造业,从医学领域到电子游戏开发,3D技术已经成为了不可或缺的一部分。在这个进程中,将3D模型导出为3D PDF格式具有重要的意义。同时,HOOPS Publish作为一个领先的解决方案,为…

Android客户端自动化UI自动化airtest从0到1搭建macos+脚本设计demo演示+全网最全最详细保姆级有步骤有图

iOS客户端自动化UI自动化airtest从0到1搭建macosdemo演示-CSDN博客 一、基础环境 1. 安装jdk 选择jdk8 如果下载高版本 可能不匹配会失败 下载.dmg文件 苹果电脑 | macOS | jdk1.8 | 环境变量配置_jdk1.8 mac-CSDN博客 Java Downloads …

08-研发流程设计(上):如何设计Go项目的开发流程?

在Go 项目开发中,我们不仅要完成产品功能的开发,还要确保整个过程是高效的,代码是高质量的。 所以,Go 项目开发一定要设计一个合理的研发流程,来提高开发效率、减少软件维护成本。研发流程会因为项目、团队和开发模式…

Android 自定义坐标曲线图(二)

Android 自定义坐标曲线图_android 自定义曲线图-CSDN博客 继上一篇文章,点击折线图上的点,显示提示信息进行修改,之前通过回调,调用外部方法,使用popupwindow或dialog来显示,但是这种方法对于弹框显示的位…

【No.21】蓝桥杯组合数学|数位排序|加法计数原理|乘法计数原理|排列数|组合数|抽屉原理|小蓝吃糖果|二项式定理|杨辉三角|归并排序(C++)

组合数学 数位排序 【问题描述】 小蓝对一个数的数位之和很感兴趣,今天他要按照数位之和给数排序。当两个数各个数位之和不同时,将数位和较小的排在前面,当数位之和相等时,将数值小的排在前面。 例如,2022 排在 409 前面, 因为 2022 的数位之和是 6,小于 409 的数位 之和 13。…

Linux 系统快速安装 nginx (新手版)

1、安装所需依赖 yum -y install pcre pcre-devel gcc openssl openssl-devel zlib zlib-devel (pcre: 包括 perl 兼容的正则表达式库 openssl: 支持安全传输协议https(和财务有关系的请求会走的协议) 创建运行用户、组 useradd -M -…

Docker实例

华子目录 docker实例1.为Ubuntu镜像添加ssh服务2.Docker安装mysql docker实例 1.为Ubuntu镜像添加ssh服务 (1)访问https://hub.docker.com,寻找合适的Ubuntu镜像 (2)拉取Ubuntu镜像 [rootserver ~]# docker pull ubuntu:latest latest: Pulling from library/ub…

JAVAEE之网络原理

1.IP地址 IP地址主要用于标识网络主机、其他网络设备(如路由器)的网络地址。简单说,IP地址用于定位主机的网络地址。 格式 IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节)&…

用户登录.java

分析: 1,用String来定义两个变量,记录正确的用户名和密码----->直接赋值得来 2,键盘录入用户名和密码------>new开辟空间得来,存的是地址值 他们直接用比较大小,必定不相同,需要用到String里面的方…

STM32CubeIDE基础学习-USART串口通信实验(中断方式)

STM32CubeIDE基础学习-USART串口通信实验(中断方式) 文章目录 STM32CubeIDE基础学习-USART串口通信实验(中断方式)前言第1章 硬件介绍第2章 工程配置2.1 工程外设配置部分2.2 生成工程代码部分 第3章 代码编写第4章 实验现象总结 …

python--os和os.path模块

>>> import os >>> #curdir #获取当前脚本的绝对路径 >>> os.curdir . >>> import os.path >>> #获取绝对路径 >>> os.path.abspath(os.curdir) C:\\Users\\GUOGUO>>> #chdir #修改当前目录 >&g…

【Python使用】python高级进阶知识md总结第6篇:线程执行带有参数的任务,1. 线程执行带有参数的任务的介绍【附代码文档】

python高级进阶全知识知识笔记总结完整教程(附代码资料)主要内容讲述:操作系统,虚拟机软件。ls命令选项,mkdir和rm命令选项。压缩和解压缩命令,文件权限命令。编辑器 vim,软件安装。获取进程编号…

MATLAB 自定义均值滤波 (53)

MATLAB 自定义均值滤波 (53) 一、算法介绍二、算法实现1.原理2.代码一、算法介绍 均值滤波,是一种常见的点云平滑算法,改善原始点云的数据质量问题,MATLAB自带的工具似乎不太友好,这里提供自定义实现的点云均值滤波算法,具体效果如下所示: 均值滤波前: 均值滤波后:…

[OAuth2]authorization_request_not_found

最近在写一套OAuth2s授权认证,当在oauth2-client调用oauth2-server,并且在点击授权以后,oauth2-client却显示【authorization_request_not_found】,并跳到了登陆页面。 经过调试发现,【authorization_request_not_fou…

Linux:TCP协议的三次握手和四次挥手

文章目录 三次握手四次挥手为什么要进行三次握手?三次握手也不安全 本篇解析的主要是TCP的三次握手和四次挥手的过程 三次握手 如图所示,在TCP要进行链接的时候,其实是要进行三次握手的 第一次握手是指,此时客户端要给服务器发送…

启信宝商业大数据助力全国经济普查

近日,合合信息旗下启信宝收到中国青年创业就业基金会感谢信,对启信宝协同助力全国经济普查和服务青年创业就业研究表达感谢。 第五次全国经济普查是新时代新征程上一次重大国情国力调查,是对国民经济“全面体检”和“集中盘点”,…

Solo 开发者周刊 (第9期):Dawwin首位人工智能编程师或将改变未来?

这里会整合 Solo 社区每周推广内容、产品模块或活动投稿,每周五发布。在这期周刊中,我们将深入探讨开源软件产品的开发旅程,分享来自一线独立开发者的经验和见解。本杂志开源,欢迎投稿。 好文推荐 Dawwin首位人工智能编程师&#…

综合实验配置

1,配置IP地址 R1: [R1]dis ip interface brief Interface IP Address/Mask Physical Protocol GigabitEthernet0/0/0 192.168.1.254/24 up up Serial4/0/0 15.1.1…

OpenHarmony OpenCV应用样例开发

背景 OpenCV 介绍 OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库。它由一系列的 C 函数和少量 C 类构成,同时提供 Python、Java 和 MATLAB 等语言的接口,实现了图像处理和计算机视觉方面…