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

死锁

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


前言

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

在上一篇文章中,通过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.mzph.cn/web/37156.shtml

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

相关文章

XML简介XML 使用教程XML的基本结构XML的使用场景

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 5、面经吐血整理的 面试技…

VMware每次打开网络设置都出现需要运行NetworkManager问题

每次打开都出现这个情况&#xff0c;是因为之前把NetworkManager服务服务关闭&#xff0c;重新输入命令&#xff1a; sudo systemctl start NetworkManager.service或者 sudo service network-manager restart 即可解决&#xff0c;但是每次开机重启都要打开就很麻烦&#xf…

【Chapter4】汇编语言及其程序设计,《微机系统》第一版,赵宏伟

一、汇编语言概述 **指令&#xff1a;**指使计算机完成某种操作的命令。 **程序&#xff1a;**完成某种功能的指令序列。 **软件&#xff1a;**各种程序总称。 **机器语言&#xff1a;**计算机能直接识别的语言。用机器语言写出的程序称为机器代码。 **汇编语言&#xff1…

Forecasting from LiDAR via Future Object Detection

Forecasting from LiDAR via Future Object Detection 基础信息 论文&#xff1a;cvpr2022paper https://openaccess.thecvf.com/content/CVPR2022/papers/Peri_Forecasting_From_LiDAR_via_Future_Object_Detection_CVPR_2022_paper.pdfgithub&#xff1a;https://github.co…

SyncUnsafeCell替换Mutex提高性能

1. 背景 在Rust开发过程中&#xff0c;很多情况下需要在不可变的情况下获取可变性或者在多线程的情况下可以安全的贡献可变数据。这种情况下我们一般使用**Mutex来实现通过加锁来实现。现在我们可以通过使用SyncUnsafeCell来替代Mutex**。 2. SyncUnsafeCell SyncUnsafeCell…

K8S之网络深度剖析(一)(持续更新ing)

K8S之网络深度剖析 一 、关于K8S的网络模型 在K8s的世界上,IP是以Pod为单位进行分配的。一个Pod内部的所有容器共享一个网络堆栈(相当于一个网络命名空间,它们的IP地址、网络设备、配置等都是共享的)。按照这个网络原则抽象出来的为每个Pod都设置一个IP地址的模型也被称作为I…

SpringBoot(一)创建一个简单的SpringBoot工程

Spring框架常用注解简单介绍 SpringMVC常用注解简单介绍 SpringBoot&#xff08;一&#xff09;创建一个简单的SpringBoot工程 SpringBoot&#xff08;二&#xff09;SpringBoot多环境配置 SpringBoot&#xff08;三&#xff09;SpringBoot整合MyBatis SpringBoot&#xff08;四…

3.ROS串口实例

#include <iostream> #include <ros/ros.h> #include <serial/serial.h> #include<geometry_msgs/Twist.h> using namespace std;//运行打开速度控制插件&#xff1a; rosrun rqt_robot_steering rqt_robot_steering //若串口访问权限不够&#xff1a…

详解PEFT库中LoRA源码

前言 GitHub项目地址Some-Paper-CN。本项目是译者在学习长时间序列预测、CV、NLP和机器学习过程中精读的一些论文&#xff0c;并对其进行了中文翻译。还有部分最佳示例教程。如果有帮助到大家&#xff0c;请帮忙点亮Star&#xff0c;也是对译者莫大的鼓励&#xff0c;谢谢啦~本…

读书笔记-《Spring技术内幕》(三)MVC与Web环境

前面我们学习了 Spring 最核心的 IoC 与 AOP 模块&#xff08;读书笔记-《Spring技术内幕》&#xff08;一&#xff09;IoC容器的实现、读书笔记-《Spring技术内幕》&#xff08;二&#xff09;AOP的实现&#xff09;&#xff0c;接下来继续学习 MVC&#xff0c;其同样也是经典…

Spring底层原理之bean的加载方式八 BeanDefinitionRegistryPostProcessor注解

BeanDefinitionRegistryPostProcessor注解 这种方式和第七种比较像 要实现两个方法 第一个方法是实现工厂 第二个方法叫后处理bean注册 package com.bigdata1421.bean;import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.…

解决idea中git无法管理项目中所有需要管理的文件

点击文件->设置 选择版本控制—>目录映射 点击加号 设置整个项目被Git管理

MySQL高级-事务-并发事务演示及隔离级别

文章目录 0、四种隔离级别1、创建表 account2、修改当前会话隔离级别为 read uncommitted2.1、会出现脏读 3、修改当前会话隔离级别为 read committed3.1、可以解决脏读3.2、会出现不可重复读 4、修改当前会话隔离级别为 repeatable read&#xff08;默认&#xff09;4.1、解决…

【论文阅读】transformer及其变体

写在前面&#xff1a; transformer模型已经是老生常谈的一个东西&#xff0c;以transformer为基础出现了很多变体和文章&#xff0c;Informer、autoformer、itransformer等等都是顶刊顶会。一提到transformer自然就是注意力机制&#xff0c;变体更是数不胜数&#xff0c;一提到…

【目标检测】DN-DETR

一、引言 论文&#xff1a; DN-DETR: Accelerate DETR Training by Introducing Query DeNoising 作者&#xff1a; IDEA 代码&#xff1a; DN-DETR 注意&#xff1a; 该算法是在DAB-DETR基础上的改进&#xff0c;在学习该算法前&#xff0c;建议掌握DETR、DAB-DETR等相关知识…

VMamba: Visual State Space Model论文笔记

文章目录 VMamba: Visual State Space Model摘要引言相关工作Preliminaries方法网络结构2D-Selective-Scan for Vision Data(SS2D) VMamba: Visual State Space Model 论文地址: https://arxiv.org/abs/2401.10166 代码地址: https://github.com/MzeroMiko/VMamba 摘要 卷积神…

axios的基本使用和vue脚手架自带的跨域问题解决

axios的基本使用和vue脚手架自带的跨域问题解决 1. axios 1.1 导入axios npm i axios1.2 创建serve1.js serve1.js const express require(express) const app express()app.use((request,response,next)>{console.log(有人请求服务器1了);console.log(请求来自于,re…

go Channel 原理 (一)

Channel 设计原理 不要通过共享内存的方式进行通信&#xff0c;而是应该通过通信的方式共享内存。 在主流编程语言中&#xff0c;多个线程传递数据的方式一般都是共享内存。 Go 可以使用共享内存加互斥锁进行通信&#xff0c;同时也提供了一种不同的并发模型&#xff0c;即通…

AI奏响未来乐章:音乐界的革命性变革

AI在创造还是毁掉音乐 引言 随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;正在逐渐渗透到我们生活的每一个角落&#xff0c;音乐领域也不例外。AI技术的引入&#xff0c;不仅为音乐创作、教育、体验带来了革命性的变革&#xff0c;更为整个音乐产业注入了…

顺序表应用——通讯录

在本篇之前的顺序表专题我们已经学习的顺序表的实现&#xff0c;了解了如何实现顺序表的插入和删除等功能&#xff0c;那么在本篇当中就要学习基于顺序表来实现通讯录&#xff0c;在通讯录当中能实现联系人的增、删、查改等功能&#xff0c;接下来就让我们一起来实现通讯录吧&a…