当前位置: 首页 > 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;再定义一个子构造函数…...

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源&#xff08;HTML/CSS/图片等&#xff09;&#xff0c;响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址&#xff0c;提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

LeetCode - 394. 字符串解码

题目 394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; 思路 使用两个栈&#xff1a;一个存储重复次数&#xff0c;一个存储字符串 遍历输入字符串&#xff1a; 数字处理&#xff1a;遇到数字时&#xff0c;累积计算重复次数左括号处理&#xff1a;保存当前状态&a…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

现代密码学 | 椭圆曲线密码学—附py代码

Elliptic Curve Cryptography 椭圆曲线密码学&#xff08;ECC&#xff09;是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础&#xff0c;例如椭圆曲线数字签…...

Android15默认授权浮窗权限

我们经常有那种需求&#xff0c;客户需要定制的apk集成在ROM中&#xff0c;并且默认授予其【显示在其他应用的上层】权限&#xff0c;也就是我们常说的浮窗权限&#xff0c;那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

数据库——redis

一、Redis 介绍 1. 概述 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的、高性能的内存键值数据库系统&#xff0c;具有以下核心特点&#xff1a; 内存存储架构&#xff1a;数据主要存储在内存中&#xff0c;提供微秒级的读写响应 多数据结构支持&…...