当前位置: 首页 > news >正文

JavaWeb8-线程安全问题

目录

1.概念

1.1.单线程

1.2.多线程

2.导致线程不安全的5个因素

①抢占式执行(首要原因)

②多个线程同时修改了同一个变量

③非原子性操作

④内存可见性

⑤指令重排序


  • 线程优点:加速程序性能。
  • 线程缺点:存在安全问题。

1.概念

线程不安全指的是程序在多线程的执行结果不符合预期。 例如:

1.1.单线程

public class ThreadDemo17 {static class Counter{//变量private int number = 0;//循环次数private int MAX_COUNT = 0;public Counter(int MAX_COUNT) {this.MAX_COUNT = MAX_COUNT;}//++方法public void incr() {for (int i = 0; i < MAX_COUNT; i++) {number++;}}//--方法public void decr() {for (int i = 0; i < MAX_COUNT; i++) {number--;}}public int getNumber() {return number;}}public static void main(String[] args) {Counter counter = new Counter(100000);//++操作counter.incr();//--操作counter.decr();//打印结果System.out.println("最终结果:" + counter.getNumber());}
}

1.2.多线程

public class ThreadDemo17 {static class Counter{//变量private int number = 0;//循环次数private int MAX_COUNT = 0;public Counter(int MAX_COUNT) {this.MAX_COUNT = MAX_COUNT;}//++方法public void incr() {for (int i = 0; i < MAX_COUNT; i++) {number++;}}//--方法public void decr() {for (int i = 0; i < MAX_COUNT; i++) {number--;}}public int getNumber() {return number;}}public static void main(String[] args) throws InterruptedException {Counter counter = new Counter(100000);Thread t1 = new Thread(() -> {//++操作counter.incr();});Thread t2 = new Thread(() -> {//--操作counter.decr();});//启动多线程进行执行t1.start();t2.start();//等待两个线程执行完t1.join();t2.join();//打印结果System.out.println("最终结果:" + counter.getNumber());}
}

每次执行结果都不同。这就是线程不安全。

2.导致线程不安全的5个因素

①抢占式执行(首要原因)

由于CPU资源较少,而线程较多,狼多肉少,发生争抢混乱。

若将上面代码改为串行执行:线程1执行完之后,线程2再执行(相当于单线程效率),就不会争抢。

public static void main(String[] args) throws InterruptedException {Counter counter = new Counter(100000);Thread t1 = new Thread(() -> {//++操作counter.incr();});t1.start();t1.join();Thread t2 = new Thread(() -> {//--操作counter.decr();});t2.start();t2.join();System.out.println("最终结果:" + counter.getNumber());}

②多个线程同时修改了同一个变量

改动代码,让不同线程各自修改各自的变量,就ok了。

public class ThreadDemo17 {static class Counter{//变量private int number = 0;//循环次数private int MAX_COUNT = 0;public Counter(int MAX_COUNT) {this.MAX_COUNT = MAX_COUNT;}//++方法public int incr() {int temp = 0;for (int i = 0; i < MAX_COUNT; i++) {temp++;}return temp;}//--方法public int decr() {int temp = 0;for (int i = 0; i < MAX_COUNT; i++) {temp--;}return temp;}public int getNumber() {return number;}}static int num1 = 0;static int num2 = 0;public static void main(String[] args) throws InterruptedException {Counter counter = new Counter(100000);Thread t1 = new Thread(() -> {//++操作num1 = counter.incr();});Thread t2 = new Thread(() -> {//--操作num2 = counter.decr();});t1.start();t2.start();t1.join();t2.join();System.out.println("最终结果:" + (num1 + num2));}
}

③非原子性操作

什么是原子性? ——将一组操作封装成一个执行单元,要一次性执行完,中间不能停顿。

一条 java 语句不一定是原子的,也不一定只是一条指令。

⽐如刚才的 n++,其实是由三步操作组成的:

  1. 从内存把数据读到 CPU
  2. 进⾏数据更新
  3. 把数据写回到 CPU

不保证原子性会给多线程带来什么问题?

如果⼀个线程正在对⼀个变量操作,中途其他线程插⼊进来了,如果这个操作被打断了,结果就可能是错误的。

这点也和线程的抢占式调度密切相关. 如果线程不是 "抢占" 的, 就算没有原⼦性, 也问题不⼤。

例如:

把⼀段代码想象成⼀个房间,每个线程就是要进⼊这个房间的⼈。如果没有任何机制保证,A进⼊房间后还没出来,B 也进⼊房间,打断 A 在房间⾥的隐私。这就是不具备原⼦性的。

如何解决?

给房间加⼀把锁,A 进去就把⻔锁上,其他⼈就进不来了。这样就保证了这段代码的原⼦性了。

有时也把这个现象叫做同步互斥,表示操作是互相排斥的。

改为串行执行即可。

④内存可见性

可见性指, ⼀个线程对共享变量值的修改,能够及时地被其他线程看到。

Java 内存模型 (JMM-JavaMemoryModel):Java虚拟机规范中定义了Java内存模型。

目的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的并发效果。

  • 线程之间的共享变量存在于"主内存" (Main Memory).
  • 每⼀个线程都有⾃⼰的 "⼯作内存" (Working Memory) .
  • 当线程要读取⼀个共享变量的时候, 会先把变量从主内存拷⻉到⼯作内存, 再从⼯作内存读取数据.
  • 当线程要修改⼀个共享变量的时候, 也会先修改⼯作内存中的副本, 再同步回主内存.

由于每个线程有自己的工作内存, 这些工作内存中的内容相当于同⼀个共享变量的 "副本". 此时修改线程1 的⼯作内存中的值, 线程2 的⼯作内存不⼀定会及时变化. 而JMM 带来的问题是会导致线程非安全问题的发生。

import java.time.LocalDateTime;/*** 内存可见性问题*/
public class ThreadDemo18 {//全局变量(类级别)private static boolean flag = true;public static void main(String[] args) {//创建子线程1Thread t1 = new Thread(() -> {System.out.println("线程 1:开始执行!" + LocalDateTime.now());while(flag) {}System.out.println("线程 1:结束执行!" + LocalDateTime.now());});t1.start();//创建子线程2Thread t2 = new Thread(() -> {//休眠1stry {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程 2:修改 flag = false!" + LocalDateTime.now());flag = false;});t2.start();}
}

线程2早已把全局变量修改为另一个值,而线程1一直在执行,它并没有感知到全局变量flag的变化,这就是内存可见性问题。

⑤指令重排序

有很多种:

  1. 编译器指令重排序
  2. 运行期指令重排序

编译器优化是一件非常复杂的事情,其本质是调整代码的执⾏顺序,保证原有逻辑不变的情况下,提高程序执行效率。这在单线程下没问题,但在多线程并发情况下容易出现混乱,从而造成线程安全问题。

相关文章:

JavaWeb8-线程安全问题

目录 1.概念 1.1.单线程 1.2.多线程 2.导致线程不安全的5个因素 ①抢占式执行&#xff08;首要原因&#xff09; ②多个线程同时修改了同一个变量 ③非原子性操作 ④内存可见性 ⑤指令重排序 线程优点&#xff1a;加速程序性能。线程缺点&#xff1a;存在安全问题。 1…...

进程切换-

实验课之前有一些问题 中断机制 第一个问题&#xff1a; interrupt的两个状态源头&#xff1a; 外中断多由随机中断&#xff08;异步中断&#xff09;造成&#xff0c;如鼠标点击&#xff0c;键盘输入&#xff1b; 内终端多由故障终端&#xff1a;程序运行异常&#xff0c;硬件…...

python--matplotlib(2)

前言 Matplotlib画图工具的官网地址是 http://matplotlib.org/ Python环境下实现Matlab制图功能的第三方库&#xff0c;需要numpy库的支持&#xff0c;支持用户方便设计出二维、三维数据的图形显示&#xff0c;制作的图形达到出版级的标准。 实验环境 Pycharm2020.2.5社区版,w…...

【李忍考研传】五、信心

这天&#xff0c;何隐一来到图书馆就一脸兴奋地对李忍说&#xff1a;“晚上告诉你一个好消息。”李忍又期待又迷惑。“小何今天是咋的了&#xff1f;买彩票中了二十&#xff1f;”虽然李忍很想知道何隐在卖什么关子&#xff0c;但是既然晚上就能知道&#xff0c;那就忍忍吧。 …...

Web 页面之间传递参数的几种方法

Web 页面之间传递参数的方法有很多种&#xff0c;下面列出一些常见的方法以及它们的代码示例。 一、前端直接传递参数 1、URL 参数传递&#xff08;query string&#xff09;&#xff1a;通过 URL 的查询字符串&#xff08;即问号后面的参数&#xff09;将参数传递给页面。可…...

Android实例仿真之二

目录 三 从无入手 第一阶段 第二阶段 第三阶段 第四阶段 第五阶段 第六阶段 第七阶段 八 举两个典型例子&#xff1a; 九 逆向工程 三 从无入手 这节标题叫从无入手&#xff0c;什么意思呢&#xff1f;如果没有Android这个实例存在&#xff0c;你要做一个类似Android…...

day47【代码随想录】动态规划之买卖股票的最佳时机III、买卖股票的最佳时机IV、最佳买卖股票时机含冷冻期、买卖股票的最佳时机含手续费

文章目录前言一、买卖股票的最佳时机III&#xff08;力扣123&#xff09;二、买卖股票的最佳时机IV&#xff08;力扣188&#xff09;三、最佳买卖股票时机含冷冻期&#xff08;力扣309&#xff09;四、买卖股票的最佳时机含手续费&#xff08;力扣714&#xff09;股票买卖问题总…...

网络数据包接收流程

1. 网络数据包接收流程简述 典型的以太网卡网络包接收流程如下&#xff1a; 1.网络包通过物理介质传到接收端的phy芯片&#xff1b; 2.phy芯片通过RGMII协议传到MAC芯片rx queue fifo中&#xff1b; 3.MAC芯片通过专用DMA将网络包搬运到网卡驱动程序预先分配好的rx ringbuffer中…...

CSAPP学习笔记——虚拟内存(二)

案例研究 Intel Core i7 该处理底层的Haswell微体系结构允许64位的虚拟和物理地址空间&#xff0c;而现在的Core i7实现支持48位&#xff08;256TB&#xff09;虚拟地址空间和52位&#xff08;4PB&#xff09;物理地址空间&#xff0c;这对目前来说已经完全够用了。&#xff…...

面试sql

创建表 create table Student ( Sno varchar(20) primary key,Sname varchar(20) UNIQUE,Ssex varchar(2),Sbirthday date,class varchar(20) )create table Course ( Cno varchar(20) primary key,Cname varchar(20) UNIQUE,Tno varchar(20) )create table Score ( …...

Python编程自动化办公案例(2)

作者简介&#xff1a;一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.前期代码 二.实现批量读取 1.os库 2.实现思路 &#xff08;1&#…...

Vulnhub 渗透练习(七)—— FRISTILEAKS: 1.3

环境搭建 下载链接 virtualbox 打开靶机设置为 host-only&#xff0c;攻击机同样。 具体可点此处 信息收集 开了个 80 端口。 用的是 apache 2.2.15 &#xff0c;这个版本有个解析漏洞。 目录 根据首页的图片猜测 /fristi/ 目录&#xff08;不过我没想到 -_-&#x…...

阶段二10_面向对象高级_分类分包思想和案例环境搭建

一.分类思想 1.分类思想概念&#xff1a; 分工协作&#xff0c;专人干专事 2.信息管理系统分类[案例] Student 类-------------------->标准学生类&#xff0c;封装键盘录入的学生信息&#xff08;id , name , age , birthday&#xff09; StudentDao 类-----------------&…...

关于打印工具print-js的使用

https://www.jianshu.com/p/f6f09dd9f7db第一步 安装组件//安装print-js npm install print-js --save //删除print-js npm uninstall print-js //安装固定版本 npm install print-js版本号 --save // 全局安装 npm install print-js --save -g第二步 引入组件安装成功后&#…...

Doxygen使用

文章目录简介Doxygen的安装Doxygen的配置生成配置文件常用配置Doxygen注释头文件注释:函数的注释:Doxygen文档生成reference简介 Doxygen 是一个流行的用于生产代码文档的工具&#xff0c;关于它的介绍可以参考官网&#xff1a;https://www.doxygen.nl/index.html。 我使用Dox…...

MySQL数据库调优————表结构设计优化

三范式 第一范式 字段具有原子性&#xff0c;即数据库表的每一个字段都是不可分割的原子数据项&#xff0c;不能是集合、数组、记录等非原子数据项当实体中的每个属性有多个值时&#xff0c;必须拆分为不同的属性 第二范式 满足第一范式的基础上&#xff0c;要求每一行数据…...

set对象和map对象

1 Set对象 介绍: Set数据结构类似数组&#xff0c;但所有成员的值唯一。 Set本身为一个构造函数&#xff0c;用来生成 Set数据结构&#xff0c;使用 add方法来添加新成员。 let a new Set(); [1,2,2,1,3,4,5,4,5].forEach(x>a.add(x)); for(let k of a){ console.log(k…...

stream()流的使用

文章目录引入流流的操作中间操作终端操作流的使用谓词筛选筛选各异的元素流的切片截断流跳过元素映射流的扁平化查找和匹配归约元素求和、最大值和最小值数值流构建流由值构建流由数组创建流引入流 java api提供的一种利用声明式的方式处理数据集合的一个东西&#xff0c;可以…...

C++学习笔记-常量

在程序执行过程中&#xff0c;其值不能改变的量称为常量(Constant)。普通常量的类型是根据数据的书写形式来决定的。如 100 是整型常量&#xff0c;0.5 是实型常量&#xff0c;‘q’ 是字符型常量&#xff0c;“qianfeng” 是字符串常量。 常量是固定值&#xff0c;在程序执行期…...

JavaScript系列之实现继承的几种方式

文章の目录一、借助父构造函数继承属性1、实现方式2、优点3、缺点二、原型链继承1、实现方式2、优点3、缺点三、组合继承四、ES6继承的实现方式参考写在最后一、借助父构造函数继承属性 1、实现方式 先定义一个父构造函数(this指向为window)&#xff1b;再定义一个子构造函数…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

使用 SymPy 进行向量和矩阵的高级操作

在科学计算和工程领域&#xff0c;向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能&#xff0c;能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作&#xff0c;并通过具体…...