专栏:JavaEE初阶起飞计划

个人主页:手握风云

一、死锁

1.1. 死锁的概念

        死锁是指两个或多个并发进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象。如果没有外力作用,这些进程将永远无法继续向前推进。

1.2. 造成死锁的原因

  • 同一把锁连续加锁两次

        由于synchronized具有可重入性,对于这种情况可以有效处理,但无法处理其他情况。

  • 两个线程两把锁,每个线程都先获取一把锁,再尝试获取对方的锁

        线程t1先拿到了locker1,线程t2也获取到了locker2。然后线程t1尝试获取locker2,线程t2尝试获取locker1。就如同把房门钥匙锁在车里面,而车钥匙又锁在家里面,就这样也构成了死锁。

public class Demo1 {public static void main(String[] args) throws InterruptedException {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(() -> {synchronized (locker1) {System.out.println("t1拿到了locker1");// 此处的sleep目的是让t1和t2分别获得对应的locker1和locker2// 如果t1和t2同时获得对应的locker1和locker2,那么t1和t2会互相等待对方释放锁,造成死锁try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2) {System.out.println("t1拿到了locker2");}}});Thread t2 = new Thread(() -> {synchronized (locker2) {System.out.println("t2拿到了locker2");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker1) {System.out.println("t2拿到了locker1");}}});t1.start();t2.start();t1.join();t2.join();}
}

        通过线程更直观的观察,我们会发现两个线程都各自卡在了尝试获取对方锁的过程中。如果没有人工干预的情况下,那么两个线程将会永远卡住。

  • N个线程M把锁

        在计算机科学中,有一个经典的并发编程问题——哲学家就餐问题:五位哲学家围坐圆桌,桌上有五碗意大利面和五把餐叉,每位哲学家两侧各有一把餐叉,哲学家只能用两侧餐叉吃面,且吃面与思考交替进行。当所有哲学家同时拿起左侧的叉子时,由于右侧叉子在别的哲学家手里,每个人就需要等待放下别人手里的叉子,此时就会死锁。

1.3. 如何避免出现死锁

        线程一旦出现死锁,线程就会卡死了,后序的逻辑也无法正常执行了,从而产生了bug。而由于死锁的出现是概率性的,虽然概率小,但是也需要重视。

  • 造成死锁的必要条件
  1. 互斥性:如果一个线程已经占用了一个资源,其他线程就不能再申请该资源,直到该资源被释放,这是死锁发生的基础。
  2. 锁不可被抢占:一旦一个线程获得了一把锁之后,它就一直拥有这把锁,其他线程要想获得,只能阻塞等待。
  3. 请求与保持:一个线程在拿到一把锁时,第一把锁还没释放,又去请求第二把锁。
  4. 循环等待:等待锁释放的条件顺序构成了循环。

        只有当以上四个条件同时满足时,才可能发生死锁。 只要破坏其中任何一个条件,就可以有效预防死锁的发生。

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) {System.out.println("t1拿到了locker1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}// locker1释放了,t2可以拿到synchronized (locker2) {System.out.println("t1拿到了locker2");}});Thread t2 = new Thread(() -> {synchronized (locker2) {System.out.println("t2拿到了locker2");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker1) {System.out.println("t2拿到了locker1");}}});t1.start();t2.start();t1.join();t2.join();}
}

二、volatile关键字

2.1. 内存可见性引起的线程安全问题

import java.util.Scanner;public class Demo3 {private static int flag = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while (flag == 0) {}System.out.println("t1线程结束");});Thread t2 = new Thread(() -> {Scanner in = new Scanner(System.in);System.out.println("请输入flag的值:");flag = in.nextInt();System.out.println("t2线程结束");});t1.start();t2.start();t1.join();t2.join();}
}

        当我们输入一个非零值时,线程2结束了,但线程1并没有正确结束。这个Bug产生的原因就是内存的可见性。t2线程中flag变量的修改,但对于t1线程“不可见了”。

        对于Java编程语言的设计者来说,考虑到一个问题:写代码的程序员的水平参差不齐。虽然有的程序员水平不高,写代码效率较低。编译器在编辑执行的时候,分析理解现有代码的意图和效果,然后自动对整个代码进行优化和调整,在确保程序执行逻辑不变的前提下,进而提高效率。但在某些特定场景下,编译器会出现误判。

        对于上面的代码来说,编译器看到的是有一个flag会快速反复读取这个内存的值。因为反复执行(读取、比较……),每次拿到的flag值是一样的,读取内存的操作相比读取寄存器会耗时很多,于是编译器就会把从内存中读取flag的操作优化掉,直接从寄存器中读取。但在t2线程中对变量flag进行了修改,编译器也不能确定t2线程里的flag到底能不能执行到,以及啥时候执行。

2.2. volatile

        通过这个关键字,提醒编译器,某个变量是“易变的”,此时就不要针对这个变量进行上述优化。

import java.util.Scanner;public class Demo3 {// 加上volatile关键字之后private static volatile int flag = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while (flag == 0) {}System.out.println("t1线程结束");});Thread t2 = new Thread(() -> {Scanner in = new Scanner(System.in);System.out.println("请输入flag的值:");flag = in.nextInt();System.out.println("t2线程结束");});t1.start();t2.start();t1.join();t2.join();}
}

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

黑暗中的爆破(船讯网Ais爬虫暨爬虫实战js逆向学习经验分享)

事先声明:本文章所获得的信息均通过合法手段获得(本人为政府部门工作,爬虫行为均经过授权),爬虫需遵守各项法律法规,不该爬取的信息不爬。 最近因为做博士毕业设计需要用到ais信息,但在船讯网爬取ais的时候遇到了问题,因为之前爬取的人太多,所以网站加上了反爬措施,c…

代码混淆的步骤

在 Android 开发中,代码混淆(ProGuard/R8)是保护代码安全和缩减应用体积的关键步骤。以下是详细的混淆流程和优化策略: 一、基础混淆步骤 1. 启用混淆 在 build.gradle 中配置: android {buildTypes {release {mini…

分布式集合通信--学习笔记

分布式集合通信一 基础概念 分布式系统模型 节点与进程模型 多机多卡、多机多进程通信模式 同步 、异步 集合通信定义 点对点通信 vs 集合通信 点对点通信 定义 :两个节点之间的直接数据传输,通常基于专用链路或网络路径通信范围:仅涉及两…

工业显示器五大品牌推荐及分析

在智能制造与工业自动化中,工业显示器扮演着至关重要的角色,最近好多朋友问我有没有什么卖工业显示的厂家推荐。那今天我为大家整理了5个工业显示器厂家品牌推荐,希望可以帮助您挑选到合适的工业显示器一、佳维视(JAWEST&#xff…

ComfyUI工作流:一键换背景体验不同场景

换背景效果展示 在图像编辑领域,背景替换是提升作品视觉效果与创意表达的重要手段。魔多 AI 社区推出的 “一键换背景” ComfyUI 工作流,凭借先进的 AI 技术与极简操作流程,为用户提供了高效、精准的背景替换解决方案。本文将从技术原理、功能…

图像旋转:从原理到 OpenCV 实践

在图像处理领域,图像旋转是一项基础且重要的操作。它不仅可以改变图像的方向,还在许多计算机视觉任务中发挥着关键作用,比如目标检测、图像配准等。本文将深入探讨图像旋转的原理,并结合 OpenCV 库提供具体的实现代码。 一、图像…

微服务架构下的抉择:Consul vs. Eureka,服务发现该如何选型?

微服务架构下的抉择:Consul vs. Eureka,服务发现该如何选型? 引言 想象一下,我们正在构建一个大型电商平台。在“双十一”大促期间,流量洪峰涌入,订单服务、商品服务、用户服务等都需要弹性伸缩&#xff…

基于Java+SpringBoot的宠物爱心组织管理系统

源码编号:S572 源码名称:基于SpringBoot的宠物爱心组织管理系统 用户类型:双角色,用户、管理员 数据库表数量:15 张表 主要技术:Java、Vue、ElementUl 、SpringBoot、Maven 运行环境:Windo…

数字样机:改写卫星物联网的研制范式

01. 卫星物联网:技术边界的自然延伸 随着物联网在城市、工业、农业等领域的广泛部署,万物互联的愿景正在不断逼近技术的边界。尤其是在海洋、沙漠、高原、边远山区等传统通信网络难以覆盖的区域,人们对无盲点物联网连接的需求日益增强。这一…

springsecurity---使用流程、加密机制、自定义密码匹配器、token字符串生成

目录 权限控制 相关框架 SpringSecurity springsecurity使用流程 1、搭建环境实现默认用户名和密码登录 2、使用数据库表中定义好的用户名和密码访问实现等值密码匹配 1)sql文件 2)搭建jdbc或者mybatis或者mybatis-plus环境 3)配置mybatis-plus环…

在 Ubuntu 22.04 上使用 Minikube 部署 Go 应用到 Kubernetes

文章目录 环境说明目标步骤与问题解决1. 构建 Go 应用和 Docker 镜像问题 1:Go 依赖下载卡住问题 2:Docker 镜像拉取失败 2. 设置 Minikube 集群安装 Minikube问题 3:Minikube 启动失败问题 4:Minikube 镜像拉取失败 3. 部署 Kube…

Android Studio-Git的使用指南

一、git的基本使用流程 git clone 克隆远程资源到本地目录,作为工作目录;然后在本地的克隆目录上添加或修改文件;如果远程修改了,需要同步远程的内容,直接git pull就可以更新本地的文件;本地在修改之后&…

【github】想fork的项目变为私有副本

在 GitHub 上,所有的 fork 都会继承其上游仓库(upstream)的可见性(visibility)设置: 可见性继承 如果你 fork 的原仓库是 public,那么你的 fork 也必须是 public。如果原仓库是 private&#xf…

微软发布新一代存储优化型虚拟机:Azure Laosv4、Lasv4 和 Lsv4 系列

微软宣布,全新一代存储优化型虚拟机——Azure Laosv4、Lasv4 和 Lsv4 系列已正式面世。 与前一代虚拟机系列相比,全新的 L 系列虚拟机实现了重大突破。它支持高达 23TB 的本地 NVMe SSD,在 CPU、网络以及远程存储性能方面均有显著提升。该系…

python调用pybind11导出的pyd,出现UnicodeDecodeError

python调用pybind11导出的pyd,出现UnicodeDecodeError 1. 问题描述 举个例子,当有以下C代码以及Pybind11的绑定代码时,在python访问包含中文的Name和Value会有UnicodeDecodeError的异常! class VxUserProp{public:VxUserProp();…

MySQL别名在GROUP BY中的使用规则

-- 设置变量:SET earliest_date ... 用于定义并赋值一个用户变量 earliest_date。 -- 用户定义的变量必须以 符号开头,例如 earliest_date。 -- 符号是MySQL中用户变量的标识符,用于区分系统变量和用户变量。 SET earliest_date (SELECT …

2025.7.4总结

感恩环节:感谢今日工作顺利度过,明天终于能美美的睡个懒觉了。感谢这周有个美好的双休。今日去实验室参观设备,感谢我的一个同事解答了我关于硬件设备与所做软件业务之间的关系,通过控制器控制网元等相关设备,同时,虽然…

Prompt 精通之路(五)- 构建你的“AI 指令系统”:超越简单提问的 CRISPE 与 APE 框架

🚀 Prompt 精通之路:系列文章导航 第一篇:[本文] AI 时代的新语言:到底什么是 Prompt?为什么它如此重要?第二篇:告别废话!掌握这 4 个黄金法则,让你的 Prompt 精准有效第…

#NFT艺术品哈希值唯一性与《民法典》“网络虚拟财产”认定的冲突

首席数据官高鹏律师数字经济团队创作,AI辅助 一、当区块链的「绝对唯一」遇上法律的「弹性空间」 每个NFT艺术品背后的哈希值,都像用数学密码刻在区块链上的指纹——世界上没有任何两个完全相同的编码。这种由0和1构筑的「数字DNA」,被技术信…

【arXiv2025】计算机视觉|即插即用|LBMamba:革新视觉模型效率,性能炸裂

论文地址:https://arxiv.org/pdf/2506.15976 代码地址:https://github.com/CiaoHe/bi-mamba 关注UP CV缝合怪,分享最计算机视觉新即插即用模块,并提供配套的论文资料与代码。 https://space.bilibili.com/473764881 摘要 Mamba…