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

目录

一、什么是线程不安全?

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

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,一经查实,立即删除!

相关文章

服务端事件(Server-Sent Events):实现实时Web通信的利器

标题&#xff1a;服务端事件&#xff08;Server-Sent Events&#xff09;&#xff1a;实现实时Web通信的利器 引言 在现代Web应用中&#xff0c;实现实时通信是一个常见需求。服务端事件&#xff08;Server-Sent Events, SSE&#xff09;是一种允许服务器主动向客户端发送数据…

【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;奈斯水印助手(小程序…

初识MATLAB相关学习笔记

MATLAB的主要功能、应用场景及其相对于其他编程语言的优势和劣势 主要功能 1. 数值计算&#xff1a; 矩阵运算和线性代数。 解微分方程组。 优化算法。 数据插值和拟合。 2. 数据可视化&#xff1a; 2D和3D图形绘制。 图像处理和分析。 动画和GUI构建。 3. 算法开发&#x…

【Linux 从基础到进阶】GlusterFS分布式文件系统搭建

GlusterFS分布式文件系统搭建 引言 随着数据存储需求的快速增长,企业和开发者越来越需要一种高效、可扩展的存储解决方案。GlusterFS是一款开源的分布式文件系统,能够将多个存储服务器组合成一个统一的文件系统,提供高可用性、弹性扩展和性能优化等特性。它可以在标准的以…

数码管进阶设计验证

前言 随着数字电路和嵌入式系统的广泛应用&#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

C#高效异步文件监控与日志记录工具

优势 异步处理&#xff1a;提高了文件变化处理的效率&#xff0c;避免了阻塞主线程。线程安全&#xff1a;使用了线程安全的队列来避免多线程环境下的竞态条件。日志记录&#xff1a;异步日志记录减少了对主线程的干扰&#xff0c;并且能够处理大量事件。灵活配置&#xff1a;…

【MySQL、Hive】分区表

SQL 本身并不直接支持多线程处理&#xff0c;因为 SQL 是一种声明式语言&#xff0c;主要用于定义和操作数据库中的数据。多线程通常是在应用程序层面实现的。然而&#xff0c;有一些方法可以在 SQL 环境中优化并发处理和提高性能&#xff0c;这些方法在某种程度上可以被视为&q…

How to stream video in a loop via RTP using ffmpeg?

ffmpeg -re -fflags genpts -stream_loop -1 -i conf2-2.mp4 -vcodec copy -an -f rtp rtp://192.168.31.152:2000 vlc 应该可以播放出来的。 这里有一篇文章&#xff0c;有兴趣的可以试试&#xff1a; https://www.wowza.com/docs/how-to-configure-vlc-media-player-for…

MySQL 学习笔记之事务操作

文章目录 MySQL 事务操作事务概述1. 事务操作的基本用法1.1 创建表和插入数据1.2 设置手动提交1.3 正常的转账操作1.4 异常情况处理1.5 使用 START TRANSACTION 2. 事务隔离级别2.1 查看当前事务隔离级别2.2 设置事务隔离级别 完整代码 MySQL 事务操作 事务概述 事务是数据库管…

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…

RedisDistributedLock 分布式锁

设计一个简单的 RedisDistributedLock 类&#xff0c;实现单例模式&#xff0c;并包含基本的锁定机制。这个类将使用 Redis 来管理锁&#xff0c;确保在分布式系统中资源的同步访问 import redis.clients.jedis.Jedis;public class RedisDistributedLock {private static Redi…