【Java】死锁问题及ThreadLocal

news2024/10/7 8:23:17

  • 什么是死锁
  • 分析过程
  • 发生死锁的原因
  • 避免死锁
  • ThreadLocal

什么是死锁

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。这是一个最严重的BUG之一

分析过程

1.一个线程一把锁
一个线程对一个锁加锁两次,如果是可重入锁,不会产生死锁,如果是不可重入锁也加不了两次,谈不上死锁。

2.两个线程两把锁
车钥匙锁家里,家里的钥匙锁车里。

复现这个现象

public class Demo05_DeadLock {
    public static void main(String[] args) {
        // 定义两个锁对象
        Object locker1 = new Object();
        Object locker2 = new Object();

        // 线程1,先获取locker1 再获取 locker2
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " t1 申请locker1..");
            synchronized (locker1) {
                System.out.println(Thread.currentThread().getName() + " t1 获取到了locker1.");
                // 模拟业务处理过程
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 获取locker2
                System.out.println(Thread.currentThread().getName() + " t1 申请locker2...");
                synchronized (locker2) {
                    System.out.println(Thread.currentThread().getName() + " t1 获取到了两把锁资源");
                }
            }
        });
        // 启动t1
        t1.start();

        // 线程2,先获取locker2 再获取 locker1
        Thread t2 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " t2 申请locker2..");
            synchronized (locker2) {
                System.out.println(Thread.currentThread().getName() + " t2 获取到了locker2.");
                // 模拟业务处理过程
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 获取locker1
                System.out.println(Thread.currentThread().getName() + " t2 申请locker1...");
                synchronized (locker1) {
                    System.out.println(Thread.currentThread().getName() + " t2 获取到了两把锁资源");
                }
            }
        });
        // 启动t2
        t2.start();
    }
}

在这里插入图片描述

发生死锁的原因

1.互斥使用:锁A被线程1占用了,线程2就不能用了;
2.不可抢占:锁A被线程1占用了,线程2不能主动把锁A抢过来,除非线程1主动释放;
3.请求保持:有多把锁,线程1拿到锁A之后不释放,还要继续再拿;
4.循环等待:线程1等待线程2释放锁,线程2要释放锁得等待线程3先释放锁,线程3释放锁得等待线程1释放锁,形成了循环关系。

🔴有这样一个经典的“哲学家吃面问题”
有个桌子, 围着一圈哲学家, 桌子中间放着一盘意大利面. 每个哲学家两两之间,放着一根筷子,每个哲学家只做两件事: 思考人生或者吃面条。思考人生的时候就会放下筷子。吃面条就会拿起左右两边的筷子(先拿起左边,再拿起右边)。如果哲学学家发现筷子拿不起来了(被别人占用了), 就会阻塞等待。
假设同一时刻, 五个哲学家同时拿起左手边的筷子,然后再尝试拿右手的筷子, 就会发现右手的筷子都被占用了。由于哲 学家们互不相让, 这个时候就形成了死锁
在这里插入图片描述

避免死锁

以上四条是死锁产生的必要条件。在死锁的情况下如果打破上述任何一个条件,便可让死锁消失。
1.互斥使用:这个不能被打破,这是锁的基本特性;
2.不可抢占:这个也不能被打破,这也是锁的基本特性;
3.请求保持:这个可以被打破,取决于代码怎么写;
4.循环等待:约定好加锁顺序就可以打破循环等待。

👀以哲学家就餐问题为例,不像以前那样先拿左手的筷子再拿右手的筷子,重新安排一下拿筷子的顺序。
1.让每个哲学家必须先去拿编号小的筷子;
2.拿到后再去拿编号大的筷子;
3.如果五个哲学家同时去拿筷子的话那么5和1都会先抢筷子1;
4.筷子1只有一个哲学家会先拿到,抢不到的那个哲学家就要等;
5.哲学家4就会获得到两只筷子,吃完面后,其他的可以获取的筷子的哲学家继续吃;
6.最终其他哲学家吃完之后,5号再获取到筷子完成吃面。
在这里插入图片描述

在复现的例子中,t1.locker1——>locker2,t2.locker2——>locker1,调整加锁顺序,就可以避免循环等待。

public class Demo05_DeadLock {
    public static void main(String[] args) {
        // 定义两个锁对象
        Object locker1 = new Object();
        Object locker2 = new Object();

        // 线程1,先获取locker1 再获取 locker2
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " t1 申请locker1..");
            synchronized (locker1) {
                System.out.println(Thread.currentThread().getName() + " t1 获取到了locker1.");
                // 模拟业务处理过程
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 获取locker2
                System.out.println(Thread.currentThread().getName() + " t1 申请locker2...");
                synchronized (locker2) {
                    System.out.println(Thread.currentThread().getName() + " t1 获取到了两把锁资源");
                }
            }
        });
        // 启动t1
        t1.start();

        // 线程2,先获取locker2 再获取 locker1
        Thread t2 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " t2 申请locker1..");
            synchronized (locker1) {
                System.out.println(Thread.currentThread().getName() + " t2 获取到了locker1.");
                // 模拟业务处理过程
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 获取locker1
                System.out.println(Thread.currentThread().getName() + " t2 申请locker2...");
                synchronized (locker2) {
                    System.out.println(Thread.currentThread().getName() + " t2 获取到了两把锁资源");
                }
            }
        });
        // 启动t2
        t2.start();
    }
}

ThreadLocal

有这样一个场景,多个班级,每个班级要统计一下班里人数,根据人数去做校服。这时可以使用ThreadLocal,不是让所有线程修改同一个共享变量,而是让每个线程修改各自的变量。

public class Demo06_ThreadLocal {
    // 初始化一个ThreadLocal
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        // 多个线程分别去统计人数
        Thread thread1 = new Thread(() -> {
            // 统计人数
            int count = 35;
            threadLocal.set(count);
//            Integer value = threadLocal.get();
//            System.out.println(value);
            // 订制校服
            print();
        }, "threadNameClass1");

        Thread thread2 = new Thread(() -> {
            // 统计人数
            int count = 40;
            threadLocal.set(count);
//            Integer value = threadLocal.get();
//            System.out.println(value);
            // 订制校服
            print();
        }, "threadNameClass2");

        thread1.start();
        thread2.start();
    }

    // 订制校服
    public static void print() {
        // 从threadLocal中获取值
        Integer value = threadLocal.get();
        System.out.println(Thread.currentThread().getName() + " : 需要订制 " + value + "套校服.");
    }
}

set()方法:
在这里插入图片描述

在这里插入图片描述


继续加油~
在这里插入图片描述

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

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

相关文章

深入理解TDD(测试驱动开发):提升代码质量的利器

在日常的软件开发工作中&#xff0c;我们常常会遇到这样的问题&#xff1a;如何在繁忙的项目进度中&#xff0c;保证我们的代码质量&#xff1f;如何在不断的迭代更新中&#xff0c;避免引入新的错误&#xff1f;对此&#xff0c;有一种有效的开发方式能帮助我们解决这些问题&a…

14.处理大数据集

14.1 随机梯度下降 假设你正在使用梯度下降来训练一个线性回归模型 当m个样本的m很大时&#xff0c;求和计算量太大了。这种梯度下降算法有另外一个名字叫做批量梯度下降&#xff08;batch gradient desent&#xff09;。这种算法每次迭代需要使用全量训练集&#xff0c;直到算…

【代码阅读软件】Source Insight 4 使用教程 | 很详细——适合新手

目录 一、概述二、常用的几个窗口&#x1f449;2.1 符号窗口&#xff08;Symbol Window&#xff09;&#x1f449;2.2 项目文件窗口&#xff08;Project Window&#xff09;&#x1f449;2.3 关系窗口&#xff08;Relation Window&#xff09;&#x1f449;2.4 上下文窗口&…

STM32--基于固件库(Library Faction)的led灯点亮

目录 一、STM32芯片的简单介绍 二、基于固件库&#xff08;Library Faction&#xff09;的led灯点亮 这是一个学习stm32的开端&#xff0c;我们由简入难&#xff0c;之前学过C51/52或是其他型号的一般都是从led开始&#xff0c;也就是简单的输入输出端口的应用。&#xff08;想…

SpringBoot整合模板引擎Thymeleaf(1)

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl Thymeleaf概述 Thymeleaf是一种用于Web和独立环境的现代服务器端的Java模板引擎&#xff0c;主要目标是将优雅的自然模板带到开发工作流程中&#xff0c;并将HTML在浏览器中…

【kubernetes】Etcd集群部署与验证

前言:二进制部署kubernetes集群在企业应用中扮演着非常重要的角色。无论是集群升级,还是证书设置有效期都非常方便,也是从事云原生相关工作从入门到精通不得不迈过的坎。通过本系列文章,你将从虚拟机准备开始,到使用二进制方式从零到一搭建起安全稳定的高可用kubernetes集…

吐血整理,性能测试Jmeter分布式压测遇坑总结+解决

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 为什么要使用分布…

JSON.parse() 全面用法介绍

JSON 通常用于与服务端交换数据。在接收服务器数据时一般是字符串。我们可以使用 JSON.parse() 方法将数据转换为 JavaScript 对象。 语法 JSON.parse(text[, reviver]) text:必需&#xff0c; 一个有效的 JSON 字符串。 reviver: 可选&#xff0c;一个转换结果的函数&#xf…

SPI协议解析

SPI协议介绍 引言介绍SPI简介物理层协议层通讯的起始和停止信号SPI 模式 优缺点优点缺点 使用例程基于STM32的SPI通信准备硬件连接 软件实现 总结 引言 SPI是串行外设接口的缩写&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线。由于SPI高速和同步的特…

vite环境变量与模式

环境变量 Vite 在一个特殊的 import.meta.env 对象上暴露环境变量。这里有一些在所有情况下都可以使用的内建变量&#xff1a; import.meta.env.MODE: {string} 应用运行的模式。 import.meta.env.BASE_URL: {string} 部署应用时的基本 URL。他由base 配置项决定。 import.m…

【ESP8266】使用MQTT协议 连接华为云iotDA,实现设备属性上报

相关资料&#xff1a;https://github.com/CQUPTLei/ESP8266 往期文章&#xff1a;【ESP8266】基础AT指令和常用WIF指令 【MQTT 5.0】协议 ——发布订阅模式、Qos、keepalive、连接认证、消息结构 一、华为云iotDA1.1 什么是iotDA1.2 创建 iotDA 产品 二、使用ESP8266上报设备…

【杂谈理解】STM32F10X标准库工程模板

前言 基于STM官网的STM32F10x标准外设库V3.6.0版本&#xff0c;文件的操作流程是参考江科大的。记录下此文方便学习和回忆。文章后也会放置完整的工程文件和意法官网下载STM32F10x标准外设库的压缩包。 流程 到意法官网下载STM32F10x标准外设库的压缩包。先找到压缩包的地址&a…

CMake详解

file文件操作 cmake的file命令_cmake file_物随心转的博客-CSDN博客 set指令 CMake中的set指令详解_cmake set_guanguanboy的博客-CSDN博客 include_directories指令 Cmake命令之include_directories介绍 - 简书 add_subdirectory Cmake命令之add_subdirectory介绍 - 简书…

两台电脑用网线传输文件的一些问题解决

两台电脑用网线传输文件 步骤如下&#xff1a; 一、两台电脑插上网线 网线568A和568B可能没什么影响 二、 ipv4地址配置 两个网线插上电脑会自动生成一个ipv4地址 cmd里使用ipconfig查看 用这个就行了如果不想用自动生成的ip地址 也可以自己配置ipv4地址和网关&#xff08…

升级Nginx

目录 前言 一、升级Nginx 1&#xff09;首先在官网下载一个新版本的Nginx 2&#xff09;首先将下载的压缩包进行解包 3&#xff09;进入已解包的目录中 4&#xff09;配置安装路径 5&#xff09;make 6&#xff09;备份原来Nginx的资源 7&#xff09;重启Nginx服务 8&#…

面向对象程序设计|运算符重载

题目一&#xff1a;分数的加减乘除&#xff08;运算符重载&#xff09; 题目描述&#xff1a; Fraction类的基本形式如下&#xff1a; 要求如下&#xff1a; 1.实现Fraction类&#xff1b;common_divisor()和contracted()函数体可为空&#xff0c;不实现具体功能。 2.编写m…

Qt QPainterPath

作用 为painter设置好绘画路径 成员函数 painter.drawPath() 1,使用当前笔画轮廓; 2,填充path指定的路径绘画出来的图形。 xxx.to() lineTo() moveTo() 使用path作画&#xff0c;一定要先将path的启动移动到需要开始绘画的点&#xff0c;否则默认从 &#xff08;0&…

【大数据之Hive】十三、Hive-HQL函数之单行函数和高级聚合函数

Hive内置函数&#xff1a;单行函数、聚合函数、炸裂函数、窗口函数。 --查看系统内置函数&#xff1a; show functions;--查看内置函数用法&#xff1a; desc function 函数名;--查看内置函数详细信息&#xff1a; desc function extended 函数名&#xff1b;一、单行函数 单行…

GDB调试大全

嵌入式开发必备工具&#xff01;&#xff01;&#xff01;学就完事了&#xff01;&#xff01;&#xff01; 目录 GDB初使用 准备条件&#xff1a;Makefile&#xff0c;section1.cpp 初步使用命令 启动调试 启动调试别传入参数 附加到进程 逐过程执行 逐语句执行 退出函…

apm装机教程(二):四旋翼

文章目录 前言一、接线二、刷固件三、设置机架四、校准遥控器五、设置遥控开关六、校准传感器七、设置参数 前言 硬件&#xff1a; pix2.4.8 250穿越机 云卓T10遥控 软件&#xff1a; APM4.3.7 QGC MP 一、接线 GPS接gps和i2c口&#xff0c;接收机的p/s口接飞控RCIN 二、刷…