多线程---线程安全问题及解决

文章目录

  • 一个线程不安全的案例
  • 造成线程不安全的原因
    • 抢占式执行
    • 多个线程修改同一个变量
    • 修改操作不是原子的
    • 内存可见性问题
    • 指令重排序问题
  • 如何让线程变得安全?
    • 加锁
      • synchronized
    • volatile

一个线程不安全的案例

题目:有较短时间让变量count从0加到10_0000

解决方案:我们创建两个线程分别让count加5_0000次

结果:count < 10_0000


class Count{public int count = 0;public void increase(){count++;}}
public class Demo {//验证线程不安全问题public static void main(String[] args) {Count count1 = new Count();// 操作同一个变量Thread thread1 = new Thread(() -> {for (int i = 0; i < 50000; i++){count1.increase();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 50000; i++){count1.increase();}});thread1.start();thread2.start();try {thread1.join();} catch (InterruptedException e) {e.printStackTrace();}try {thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(count1.count);   //  <100000}
}

造成线程不安全的原因

抢占式执行

操作系统调度线程的时候,是一个“随机”的过程,当两个线程都参与调度的时候,谁先谁后不确定。

多个线程修改同一个变量

线程之间是“并发执行的”,当多个线程修改同一个变量时,多个线程同时获取到了变量值。某一个线程修改了变量,修改的结果不能被其他线程知道,其他线程还会修改原先获取到的值,导致结果错误。

如果修改的是不同的变量,线程之间独立执行,不会出现问题。

修改操作不是原子的

count++操作底层是三条指令在CPU上完成的:

load:把内存中的值读到CPU寄存器中;
add:count+1;
save:把寄存器的值写回内存

由于这三条指令不是原子的,两个线程在执行时就会有不同的执行顺序:
在这里插入图片描述
在这些执行顺序下,都会使count没有正确的++,使最终结果出错。

内存可见性问题

JVM优化引入的BUG。例如,两个线程在操作同一个变量,一个线程读并且比较,一个线程修改。假设读操作非常频繁的情况下,比较操作也会非常的频繁。但是读是从内存中读,比较是在CPU里比较。比较的速度远远大于读的速度。而且每次读到的值还一样,这时编译器就会大胆优化:只读取一次,后面就不从内存中读了。每次比较都和前面读取到的值比较,不和内存中的值比较。这时另一个线程把内存中的值修改了但是这个线程比较的还时原来的值,就会有问题。

指令重排序问题

JVM优化引入的BUG。由我们自己写的代码在大多数情况下的执行流程中,指令的执行顺序往往都不是最优选择,即没有使运行速度达到最快。因此,JVM在编译时,就会在逻辑等价的前提下,对我们的指令进行重新排序使代码的运行速度变快。

这样的优化在单线程时,是没有问题的。但是在多线程的情况下,线程之间是抢占式执行的,哪条指令先执行哪条指令后执行不确定,就可能有问题。

如何让线程变得安全?

“抢占式执行”是线程调度的基本方式,我们无法干预。

“多个线程修改同一个变量”:我们在特定场景下就是得修改同一个变量,也无法改变。

“操作不是原子的”:我们保证线程安全的主要方式,通过synchronized加锁。

“内存可见性”“指令重排序”:JVM优化的问题。使用volatile解决

加锁

锁,具有独占的特性。如果当前锁还没有被加上,加锁操作就能成功;如果锁已经被人加上了,加锁操作就不能成功,会进入阻塞等待的状态。即:加锁操作会让”并发执行“变成”串行执行“

在这里插入图片描述

注:

  1. 处于同一个规则:所有线程都会加锁的情况下,只有获取到锁才能执行相关操作。
  2. 一个线程加锁,另一个线程不加锁。这样不能保证线程安全。 因为它俩没有采用同一个规则办事儿,不会产生锁竞争。

synchronized

加锁操作是通过synchronized关键字来实现的,只要对同一个对象加锁,就会产生“锁竞争”。而synchronized有三种用法:

  • synchronized修饰代码块

synchronized修饰代码块时,必须指定锁对象。如果锁对象是this,即对当前对象加锁。
像下面的代码,count调用了increase就是对count加锁,对同一个对象加锁会出现“锁竞争”

	public void increase(){synchronized (this){count++;}}Count count = new Count();Thread thread1 = new Thread(() -> {for (int i = 0; i < 50000; i++){count.increase();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 50000; i++){count.increase();}});

当两个线程针对两个对象加锁时,不会产生“锁竞争”,不会阻塞等待。

	public void increase(){synchronized (this){count++;}}Count count1 = new Count();Count count2 = new Count();Thread thread1 = new Thread(() -> {for (int i = 0; i < 50000; i++){//对count1加锁count1.increase();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 50000; i++){//对count2加锁count2.increase();}});
  • synchronized修饰普通方法

synchronized修饰普通方法时,相当于是锁this对象,只要是同一个对象调用到这个方法都会产生“锁竞争”。

	public synchronized void increase(){count++;}Count count = new Count();Thread thread1 = new Thread(() -> {for (int i = 0; i < 50000; i++){ count.increase();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 50000; i++){count.increase();}});
  • synchronized修饰静态方法

静态方法是随着类加载而加载的而且只加载一次,所以静态方法是和类绑定在一起的只有一份。

	public static Object locker = new Object();public void increase(){synchronized(locker){count++;}}Count count = new Count();Count count1 = new Count();//count和count1都是Count类的对象  里面的静态方法是同一个。//所以它俩虽然是不同的对象  但是也会产生“锁竞争”Thread thread1 = new Thread(() -> {for (int i = 0; i < 50000; i++){ count.increase();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 50000; i++){count1.increase();}});

Count.class是对类对象加锁,而类也只有一份儿。静态方法也是只有一份儿,所以它们俩也会产生“锁竞争”

	public static Object locker = new Object();public void increase(){synchronized(locker){count++;}}public void increase1(){synchronized(Count.class){count++}}Count count = new Count();Count count1 = new Count();//count和count1都是Count类的对象 类只有一个,静态方法也是只有一个。它们俩绑定在一起//所以它俩虽然是不同的对象  但是也会产生“锁竞争”Thread thread1 = new Thread(() -> {for (int i = 0; i < 50000; i++){ count.increase();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 50000; i++){count1.increase();}});

volatile

    public volatile int count = 0;

volatile只有一个用法就是修饰变量,表示该变量的值必须从内存中读取,不能从缓存中读取。
即:volatile禁止了编译器优化,避免了直接读取CPU寄存器中缓存的数据,而是每次都读取内存。

但是volatile并不能保证是操作是原子性的,因此,它只适合用于一个线程读,一个线程修改的场景。不适合用于两个线程都修改的场景。

谈到volatile就会联想到JMM(Java Memory Model):Java内存模型

在JMM中,引入了新的术语:
工作内存(work memory):即CPU寄存器(缓存)
主内存(main memory):真正读取的内存

站在JMM的角度看待volatile:
正常程序的执行过程中,先会把主内存的数据加载到工作内存中,再进行计算处理。编译器优化可能会导致不是每次都会真正的读取主内存,而是直接读取工作内存中的缓存数据,就可能导致内存可见性问题。volatile起到的效果就是保证每次读取数据都是真的从主内存中重新读取。

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

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

相关文章

行业追踪,2023-10-26

自动复盘 2023-10-26 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

音视频开发常见问题(五):视频黑屏

摘要 本文介绍了视频黑屏的可能原因和解决方案。主要原因包括用户主动关闭视频、网络问题和渲染问题。解决方案包括优化网络稳定性、确保视频渲染视图设置正确、提供清晰的提示、实时监测网络质量、使用详细的日志系统、开启视频预览功能、使用视频流回调、处理编解码问题、处…

Reactor反应器模式

文章目录 一、单线程Reactor反应器模式二、多线程Reactor反应器模式 在Java的OIO编程中&#xff0c;最初和最原始的网络服务器程序使用一个while循环&#xff0c;不断地监听端口是否有新的连接&#xff0c;如果有就调用一个处理函数来处理。这种方法最大的问题就是如果前一个网…

分享一波操作系统、谢希仁版本计算机网络学习笔记【思维导图】

操作系统复习笔记 - 幕布第一章引论第二章处理器管理进程同步与通信https://www.mubu.com/doc/58qrnf20ndg 大纲 - 幕布物理层数据链路层网络层https://www.mubu.com/doc/1eo9_8TyUdg计算机网络-语雀https://www.yuque.com/yuqueyonghu6nc56e/dgg1dl/wx34gx72xpgmt598?singleD…

HackTheBox-Starting Point--Tier 1---Crocodile

文章目录 一 题目二 实验过程 一 题目 Tags Web、Network、Custom Applications、Protocols、Apache、FTP、Reconnaissance、Web Site Structure Discovery、Clear Text Credentials、Anonymous/Guest Access译文&#xff1a;Web、网络、定制应用程序、协议、Apache、FTP、侦…

Python的random随机模块相关学习记录

random是有关随机功能的一个内置模块 import random# 获取0-1之间的随机小数 print(random.random()) # 0.6224750165089413 # 获取0-1之间的随机小数# a-----b之间的随机小数 a 0 b 10 print(random.uniform(a, b)) # 1.25491670861257# 两边的值都包含在内&#xff0c;获…

html和css中图片加载与渲染的规则是什么?

浏览器渲染web页面的过程 解析html&#xff0c;构成dom树 2.加载css&#xff0c;构成样式规则树 3.加载js&#xff0c;解析js代码 4.dom树和样式树进行匹配&#xff0c;构成渲染树 5.计算元素位置进行页面布局 5.绘制页面&#xff0c;呈现到浏览器中 图片加载和渲染的过程 1.解…

java后端请求过滤options方式,亲测有效

前端每次发出post 请求时&#xff0c;浏览器会默认请求2次&#xff0c;一次是options类型&#xff0c;一次是真实的请求&#xff0c;为了避免这种情况发生&#xff0c;需在后端过滤器中拦截下options请求&#xff0c;代码如下&#xff1a; import java.io.IOException; import …

华为eNSP配置专题-策略路由的配置

文章目录 华为eNSP配置专题-策略路由的配置0、概要介绍1、前置环境1.1、宿主机1.2、eNSP模拟器 2、基本环境搭建2.1、终端构成和连接2.2、终端的基本配置 3、配置接入交换机上的VLAN4、配置核心交换机为网关和DHCP服务器5、配置核心交换机和出口路由器互通6、配置PC和出口路由器…

【软件安装环境配置】vscode 安装界面没有出现安装路径的选择 的解决,以及vscode的删除的问题

由于vscode 没有删除干净&#xff0c;就会出现vscode 安装的时候&#xff0c;没有出现安装路径的界面&#xff0c;所以可以来到vscode的安装路径&#xff0c;点击 unins000.exe 文件就可以 实现将vscode 相关的文件删除&#xff0c; 如果是删除了整个vscode 安装下的文件&…

Win11 安装wsl遇到的问题解决

Win11 安装wsl遇到的问题解决 Win11 安装wsl遇到的问题解决WslRegisterDistribution failed:0x8007019eWslRegisterDistribution failed:0x800701bcUbuntu换源WSL通过网络访问Windows Win11 安装wsl遇到的问题解决 WslRegisterDistribution failed:0x8007019e 参考Link WslR…

软考高项-计算题(3)

题10 问题一 EV50*0.525 问题二 EACBAC/CPI CPIEV/AC25/28 EAC50*28/2556 问题三 因为CPI<1&#xff0c;所以项目实际费用超支 题11 PV2000500010000750006500020000177000 AC2100450012000860006000015000179600 EV200050001000075000*0.965000*0.720000*0.351370…

智能终端界面自动化测试操作工具 - Appium常见用法

1. Appium 是什么可以做什么&#xff1f; Appium 是一款开源的移动应用自动化测试框架&#xff0c;用于测试移动应用程序的功能和用户界面。它支持多种移动平台&#xff0c;包括 Android 和 iOS&#xff0c;可以使用多种编程语言进行脚本编写&#xff0c;如 Python、Java、Jav…

网络协议--TCP的成块数据流

20.1 引言 在第15章我们看到TFTP使用了停止等待协议。数据发送方在发送下一个数据块之前需要等待接收对已发送数据的确认。本章我们将介绍TCP所使用的被称为滑动窗口协议的另一种形式的流量控制方法。该协议允许发送方在停止并等待确认前可以连续发送多个分组。由于发送方不必…

SpringMVC Day02 : 请求方式

前言 欢迎阅读 Spring MVC 系列教程的第二篇文章&#xff01;在上一篇文章中&#xff0c;我们介绍了 Spring MVC 的基本概念和使用方法。今天&#xff0c;我们将深入探讨 Spring MVC 中不同的请求方式&#xff0c;以及如何在你的应用程序中正确地处理它们。 在 Web 开发中&am…

nlp与知识图谱代码解读_词嵌入

目录 词嵌入简单原理代码案例解读专业原理介绍场景 词嵌入 简单原理 可以使用一些比喻和生活中的例子&#xff1a; 老师&#xff1a; 你们还记得玩乐高积木的时候&#xff0c;每个积木块代表了一个特定的事物或形状吗&#xff1f;现在&#xff0c;想象一下&#xff0c;每个词…

day01:数据库DDL

一:基础概念 数据库:存储数据的仓库&#xff0c;数据是有组织的进行存储 数据库管理系统:操纵和管理数据库的大型软件 SQL&#xff1a;操作关系型数据库的编程语言&#xff0c;定义了一套操作关系型数据库统一标准 关系图 二:数据模型 关系型数据库:建…

vue的双向绑定的原理,和angular的对比

目录 前言 Vue的双向绑定用法 代码 Vue的双向绑定原理 Angular的双向绑定用法 代码 Angular的双向绑定原理 理解 图片 关于Vue的双向绑定原理和与Angular的对比&#xff0c;我们可以从以下几个方面进行深入探讨&#xff1a; 前言 双向绑定是现代前端框架的核心特性之…

经典卷积神经网络 - ResNet

ResNet是一种残差网络&#xff0c;咱们可以把它理解为一个子网络&#xff0c;这个子网络经过堆叠可以构成一个很深的网络。 我们一直在加深神经网络&#xff0c;但是加深不一定只会带来好处。 残差块 串联一个层改变函数类&#xff0c;我们希望能扩大函数类残差块加入快速通…

计算机网络【CN】子网划分与子网掩码

一个子网定义(X.X.X.X/n) 子网掩码为 n 个 1&#xff0c;32-n 个 0包含的 IP 地址数&#xff1a;232−n 主机号全 0 表示本网段主机号全 1 表示网段的广播地址可分配的 IP 地址数 :232−&#x1d45b;−2 子网划分原则 满足子网定义子网&#x1d434;1…&#x1d434;&#x…