线程安全是什么问题?如何引起?死锁是啥?如何解决?

目录

一、什么是线程不安全?

二、如何引起的线程安全?怎么解决?

1)CPU调度执行是随机的,抢占式执行(根本原因,硬件层面咱们无法干预)

2)多个线程,对同一变量进行修改

3)修改操作中, 不是“原子”的(重点)

死锁是啥,怎么引起的?  (重点)

4)内存可见性

5)指令重排序 

 总结-保证线程安全的思路


一、什么是线程不安全?

 线程不安全:

是多个线程并发执行,而产生的结果和我们预期的不相同,这种bug是由多线程引起的,所以我们叫线程安全问题,也就是线程不安全。

 比如看下列代码:

public class demo8 {private static int count=0;public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{for(int i=0;i<50000;i++){count++;}});Thread t2=new Thread(()->{for(int i=0;i<50000;i++){count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println("count="+count);}
}

我们预期是t1线程50000次,t2线程50000次,加起来一共100000次。这个是我们预期的结果。但是真实的情况如下: 

是79506次,而且每一次的结果都不一样,这样和预期不符合,就是线程安全问题。

 为什么会出现这样的情况呢?如果是单线程会如此吗?请看下文:

二、如何引起的线程安全?怎么解决?

 上面那个count的例子来解释:

我们知道CPU把线程调度上去执行,最小单位是指令,我们count++其实是3个指令:

1)把值从内存中读取到CPU的寄存器上(load)

2)在寄存器上把count+1(add)

3)把寄存器加完之后的值传到内存中(save)

当然上面那样,是我们最理想,也是我们想要的状态,但是在多线程的加持下,因为CPU的调度是抢占式的,是随机的,我们并不能操控,可能刚执行了一半就给调走了。我们基本上很难遇到上面的情况,可能会出现以下的情况(简单列举了几种):

最小单位是指令,CPU不会执行半个指令,会给你执行完一个,但是count++有3个指令,我也不知道CPU什么时候调度走,如何给别人执行,这样就会造成bug,也就是我们说的线程安全问题!

既然我们明白了什么是线程安全,下面是五种会出现线程安全问题的原因,我们可以思考下面五种原因,从而去解决线程安全问题,或者去避免写出错误的代码:

1)CPU调度执行是随机的,抢占式执行(根本原因,硬件层面咱们无法干预

CPU的调度执行具体的可以参考我往期的博客,这里就不赘述了。

2)多个线程,对同一变量进行修改

这个行不行,具体要看业务需求,但是对于Java来说,这并不是一个很好的解决方法,业务还有更好的方法。

如果是单个线程,对一变量修改,那就没有关系,不会出线程安全的BUG。

3)修改操作中, 不是“原子”的(重点)

 这个来说,难道就是把类似像count++这类不是原子的,变为原子的吗?答:是的。

那咱们要怎么变啊?一条代码写多条?

答:给它加锁! (相当于一夫一妻制,锁上其他线程就进不来了,只能等他释放锁,其他线程才能进去)

 如何使用锁?语法是啥?

关键字:synchronized

Object locker=new Object();
synchronized(locker){....}

 如图

也就是: 既然大家都是串行化了,那效率会不会大大降低呢?

答:不会,因为这只是小部分需要加锁,其他代码都是不需要的。也就少了那么一点点,对于计算机来说,这些都是小case

注意事项:

1. 这个得都是同一个锁对象(才会发生锁竞争,就是堵塞),也就是传参(我这写为locker),如果一个是locker1一个是locker2,相当于2个厕所,互不影响,都能上厕所。

2.传参(locker)只要不是int,char这种简单类型,其他object子类都行,甚至写class对象都行

3.想要相互不影响的线程必须都得加锁,就是t1和t2的count都得加锁,相当于限制t1和t2只能上这个厕所,有一个人就得排队。如果只是一个加锁,t1打包好的3条指令很大程度会插入t2那3条指令中间。(如上图)

4.如果是多个线程一起加锁,t1执行完了,t2和t3谁先执行呢?是随机执行,抢占式调度。

5.synchronized还可以修饰方法

6.还可以修饰静态的方法,但是要用类对象做锁对象

7.用锁要考虑清楚,不然到时候某个线程堵塞了,什么时候恢复就不知道了

8.可重入锁,可连续加锁两次/多次,遇到加锁的会放行,不加第二次。(请看下面关于死锁的内容)

死锁是啥,怎么引起的?  (重点)

Ⅰ:针对一把锁连续加锁两次

如上图,如果连续加两次锁,那么就会构成一个死循环,也就成了死锁。

但是要注意的是,在Java中synchronized可以自动判断,如果有加锁过了,是同一个锁对象的话,则在下次加锁的时候会放行,这也叫可重入锁。不会真正的报错,抛异常。 

Ⅱ:两个线程两把锁

1)线程1在对A加锁,线程2对B加锁,

2)线程1在不释放A的情况下,要对B加锁;同时线程2在不释放B的情况下,对A加锁。

依照上面的情况,势必会僵持住,造成死循环,这也是死锁。

比如:

我和朋友在吃饺子,有醋有辣椒可以蘸饺子,我想拿醋放了点,朋友拿了辣椒放了点。这时候我又想要辣椒了,他又想用醋。这时他说你把醋给我,我再把辣椒给你。我说你先给我辣椒,我在给你醋。这时候我们就会僵持不下(当然这件事情可以商量,但是在程序中,代码的执行是死板的,就会变成死锁)

上述代码就是个例子。t1不释放locker1又要locker2,t2不释放locker2又要locker1,两个线程谁也不服谁。

Ⅲ:M个线程M把锁

和2个线程2把锁类似。哲学家都在等右手边的筷子,才能拿起筷子吃面条,但是没人放下右手边的筷子(因为他们也在等他们右手边的筷子才能吃完,吃完才能放下来)都僵持住了。

死锁的四个必要条件:

(1)锁是互斥的(锁的基本特性)

(2)锁是不可被抢占的(锁的基本特性)

比如线程1加锁了,没释放的前提下,线程2不能去抢

(3)请求与保持(代码结构)

比如线程1加锁A了,没释放A的前提下(在保持着),去加锁B(请求)

(4)循环等待/环路等待/循环依赖

多个线程获取锁,就可能会陷入死循环

由于1和2是锁的基本特性,所以我们无法避免。但是对于3来说,有些时候,我们是需要写成3那样的结构的。所以就要看4,只要4不成立,就不会产生死锁。 

如何用4解决呢?

答:约定加锁的顺序,编号小的先加锁,如何编号大的再加锁

比如上面的代码,我们先让t1加锁locker1和locker2,再让t2加锁locker1和locker2。这样按顺序加锁就不会产生死锁了。

4)内存可见性

请看下图:

写了2个线程,想要t2输入值修改了n之后,t1的while中结束,因为n不为0了。但是我们输入后会发现并不会结束。为什么?这就是因为内存可见性  

内存可见性同时也是bug,也是线程安全问题。但这并不是我们代码书写,而是JVM自己的问题,是JVM自己优化,优化出了问题。

这里的快慢并不是绝对速度的快慢,而是相对速度的快慢:

内存不可见性原因及原理:

JVM发现执行这个代码的时候,发现每次循环的过程中,执行1)操作,开销非常大,而且每次执行1)的结果都是一样的。

并且JVM根本没有意识到,用户未来会修改这个n,于是JVM就做了一个大胆的操作,直接把1)这个操作优化了。

也就是每次循环,不会重新读取内存中的数据,而是直接读取寄存器/cache中的数据。当做了这个操作后,循环的开销也就大大降低了,

但是t2对于在内存中的修改,t1是感知不到的,所以也就是t1的在“内存不可见性”。

为啥JVM要优化代码呢?

答:因为程序员的编程水平是参差不齐的,为了减少程序员的差距,降低程序员的门槛,JVM就会优化代码,让厉害的程序员和一般的程序员不会差距太明显。 

如何解决内存可见性问题?

加入关键字:volatile,也就是在n那里加入volatile

 在n上面加个volatile就能解决这个问题,volatile(易变的),也就是告诉JVM这个n是易变的,让JVM不要优化这个代码,让它在内存的修改能被读取到。 t1也就不会直接在寄存器上读取了。

5)指令重排序 

 指令重排序也是JVM自己优化出来的一个bug

比如你妈妈让你去菜单给你个清单,如下 

如果你老老实实按清单的买,那就太慢了,而且要走很久,如果你调整一下顺序,则就能快很多,那么JVM也是这么想的,把指令重新调整一下,提高效率。这就叫做指令重排序。

这个是一个单例模式中懒汉模式的代码,一个程序只需要一个对象,所以如果没有创建,我可以创建出来一个,如果有了,我返回这个对象就行了。两层if是不一样的功能,第一层主要是为了不频繁加锁消耗资源(因为如果instance没被new出来,则需要加锁给new出instance,不然可能会被其他线程打断)。第二层是实现唯一的实例,new过就直接return就好了。

为什么这个代码也算线程不安全呢?

 我们的JVM在执行new操作的时候,会把2和3调整一下

 既然这个问题这么可怕,我们怎么解决这个问题呢?

答:用关键字volatile。

只要我们加上volatile,就告诉JVM这个instance是易变的,让JVM不要去优化它,这样就可以解决指令重排序的问题了。

 总结-保证线程安全的思路

1. 使⽤没有共享资源的模型

2. 适⽤共享资源只读,不写的模型

        a. 不需要写共享资源的模型

        b. 使⽤不可变对象

3. 直⾯线程安全(重点)

        a. 保证原⼦性

        b. 保证顺序性

        c. 保证可⻅性

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

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

相关文章

【Hot100】LeetCode—105. 从前序与中序遍历序列构造二叉树

目录 1- 思路递归 2- 实现⭐105. 从前序与中序遍历序列构造二叉树——题解思路 3- ACM 实现 原题连接&#xff1a;105. 从前序与中序遍历序列构造二叉树 1- 思路 递归 前序&#xff1a;中左右中序&#xff1a;左中右 让前序的第一个元素作为中序的分割点 分割思路 1- 递归…

做个实验

做个实验 #include <bits/stdc.h> using namespace std; #define int long long #define ll __int128_t #define ar array<int, 2> #define arr array<int, 3> int n, m, k, inf 1LL << 61, mod 998244353;// 1e97; const int N 5e5 50;void sol…

使用gitee存储项目

gitee地址&#xff1a;Gitee - 基于 Git 的代码托管和研发协作平台 创建gitee远程仓库 将远程仓库内容拉取到本地仓库 复制下面这个地址 通过小乌龟便捷推送拉取代码&#xff1a;https://blog.csdn.net/m0_65520060/article/details/140091437

基于51单片机的百叶窗proteus仿真

地址&#xff1a;https://pan.baidu.com/s/19M6jeTIHJcyDBGNx4H9nTA 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectron…

RabbitMQ的核心概念

RabbitMQ是一个消息中间件&#xff0c;也是一个生产者消费者模型&#xff0c;负责接收&#xff0c;存储和转发消息。 核心概念 Producer 生产者&#xff0c;是RabbitMQ Server的客户端&#xff0c;向RabbitMQ发送消息。 Consumer 消费者&#xff0c;是RabbitMQ Server的客…

快手怎么免费的去掉视频水印?分享这三个工具给你

​ 我们经常会遇到想要保存的视频带有水印&#xff0c;这不仅影响美观&#xff0c;也不利于分享。为了解决这个问题&#xff0c;我将分享三个免费去除视频水印的工具&#xff0c;帮助你轻松去除水印&#xff0c;享受无干扰的视频体验。 工具一&#xff1a;奈斯水印助手(小程序…

数码管进阶设计验证

前言 随着数字电路和嵌入式系统的广泛应用&#xff0c;数码管作为一种常见的显示设备&#xff0c;在各种电子产品中扮演着重要角色。数码管以其结构简单、显示清晰和成本低廉的特点&#xff0c;广泛应用于计数器、时钟、测量仪器等领域。然而&#xff0c;传统的数码管设计通常仅…

DBeaver安装使用

文章目录 简介支持的数据库支持的系统 下载安装DBeaver使用修改Maven下载jar地址窗口->首选项连接->驱动->Maven配置仓库地址 选择需要连接的数据库进行连接 简介 DBeaver 是一个通用的数据库管理工具和 SQL 客户端&#xff0c;支持 MySQL, PostgreSQL, Oracle, DB2,…

运维学习————nginx2-配置详解及负载均衡

目录 一、配置文件详解 1.1、结构 1.2、重要配置解释 1.3、详细配置 全局配置 Events HTTP 服务器配置 server虚拟主机配置 location URL匹配配置 1.4、完整配置 二、负载均衡 2.1、概念 2.2、集群规划及实现 2.3、具体实现 2.3.1、克隆 2.3.2、修改tomcat1配…

Python | Leetcode Python题解之第372题超级次方

题目&#xff1a; 题解&#xff1a; class Solution:def superPow(self, a: int, b: List[int]) -> int:MOD 1337ans 1for e in b:ans pow(ans, 10, MOD) * pow(a, e, MOD) % MODreturn ans

go+gin+vue入门

后端框架 1、安装go、goland 2、创建空项目 3、下载要用的包&#xff1a;命令行输入go get -u github.com/xxxx 4、安装mysql数据库&#xff0c;使用navicat创建数据库。 5、按照项目框架搭建目录、文件、代码&#xff1a;如router、model… 6、运行测试&#xff0c;go run ma…

云原生之全链路分布式跟踪系统 Zipkin和SkyWalking

贪多嚼不烂 Pinpoint 就不对比了 参考 APM系统简单对比(zipkin,pinpoint和skywalking) springcloud 看云 Zipkin和SkyWalking都是流行的分布式跟踪系统&#xff0c;但它们的设计和实现有明显的不同。 以下是它们之间的一些对比&#xff1a; 数据存储&#xff1a; Zipk…

OpenCV(开源计算机视觉库)

OpenCV&#xff08;开源计算机视觉库&#xff09;是一个专注于实时计算机视觉的全面库&#xff0c;包含了丰富的工具和功能。以下是 OpenCV 中一些关键知识点的详细列表&#xff1a; 核心功能 基本结构&#xff1a;Mat、Scalar、Point、Size、Rect 等。 图像 I/O&#xff1a;读…

# 利刃出鞘_Tomcat 核心原理解析(八)-- Tomcat 集群

利刃出鞘_Tomcat 核心原理解析&#xff08;八&#xff09;-- Tomcat 集群 一、Tomcat专题 - Tomcat集群 - 介绍及准备工作 1、Tomcat集群 简介 由于单台Tomcat的承载能力是有限的&#xff0c;当我们的业务系统用户量比较大&#xff0c;请求压力比较大时&#xff0c;单台Tomc…

macOS安装搭建python环境

安装Homebrew apt-get是一个常见于Debian和Ubuntu等基于Linux的操作系统中的包管理工具&#xff0c;用于安装、更新和移除软件包。然而&#xff0c;macOS使用的是Homebrew或者MacPorts等其他的包管理工具&#xff0c;并不使用apt-get。 如果你想在macOS上使用类似apt-get的功…

Python版《超级玛丽+源码》-Python制作超级玛丽游戏

小时候最喜欢玩的小游戏就是超级玛丽了&#xff0c;有刺激有又技巧&#xff0c;通关真的很难&#xff0c;救下小公主还被抓走了&#xff0c;唉&#xff0c;心累&#xff0c;最后还是硬着头皮继续闯&#xff0c;终于要通关了&#xff0c;之后再玩还是没有那么容易&#xff0c;哈…

<C++> 二叉搜索树

目录 二叉搜索树 1. 概念 2. 二叉搜索树操作 2.1 基础结构 2.2 非递归版 1. 查找 2. 插入 3. 删除 2.3 递归版 1. 查找 2. 插入 3. 删除 2.4 拷贝构造函数 2.5 赋值运算符重载 2.6 析构函数 2.7 完整代码 3. 二叉搜索树的应用 4. 二叉搜索树的性能 二叉搜索树 1. 概念 二叉搜索…

Debug-023-Document.createElement()的使用

Document.createElement() document.createElement()是在对象中创建一个对象&#xff0c;要与appendChild() 或 insertBefore()方法联合使用。 appendChild() 方法在节点的子节点列表末添加新的子节点。 insertBefore() 方法在节点的子节点列表任意位置插入新的节点。 用途举…

笔记整理—uboot启动过程(3)栈的二次设置以及常用名词解析,BL1部分完

前文说到了uboot的lowlevel_init都干了些什么&#xff0c;也就是经过了这项初期的低级启动&#xff0c;使得我们能在串口监视器上看见机器打印出的第一句话“OK”。当lowlevel_init结束后&#xff0c;uboot去做了另一件事情&#xff0c;那就是栈的再次设置。 第一次栈设置发生在…

解决Qt多线程中fromRawData函数生成的QByteArray数据不一致问题

解决Qt多线程中fromRawData函数生成的QByteArray数据不一致问题 目录 &#x1f514; 问题背景&#x1f4c4; 问题代码❓ 问题描述&#x1fa7a; 问题分析✔ 解决方案 &#x1f514; 问题背景 在开发一个使用Qt框架的多线程应用程序时&#xff0c;我们遇到了一个棘手的问题&…