<JavaEE> 什么是线程安全?产生线程不安全的原因和处理方式

目录

一、线程安全的概念

二、线程不安全经典示例

三、线程不安全的原因和处理方式

3.1 线程的随机调度和抢占式执行

3.2 修改共享数据

3.3 关键代码或指令不是“原子”的

3.4 内存可见性和指令重排序

四、Java标准库自带的线程安全类


一、线程安全的概念

线程安全是指:某段代码无论是在单线程还是多线程的环境下执行,结果都是正确的或符合心理预期的。
通常情况下,如果一段代码在单线程环境下执行和在多线程环境下执行的结果不一致,那么就很可能存在线程安全的问题,这个情况就称为“线程不安全”。
线程安全问题是多线程编程的重点!

二、线程不安全经典示例

class AddTest{public static  int count = 0;public void add(){count++;}
}
public class Synchronized_Demo0 {public static void main(String[] args) throws InterruptedException {//创建一个AddTest实例;AddTest test1 = new AddTest();//两个循环调用add方法进行count++的线程;Thread t1 = new Thread(()->{for (int i = 0; i < 5000; i++) {test1.add();}});Thread t2 = new Thread(()->{for (int i = 0; i < 5000; i++) {test1.add();}});//启动线程;t1.start();t2.start();//阻塞main线程;t1.join();t2.join();//打印count;System.out.println("count = :"+AddTest.count);}
}//第一次运行结果:
count = 8061
//第二次运行结果:
count = 7269
//第三次运行结果:
count = 9792

在单线程环境下,两个5000次循环的count++,得到的结果应该是count = 10000。

上述代码在多线程的环境下,得到的结果却是count = 8061。

而且,每次运行得到的结果都会不同。
这就出现了线程安全问题。

三、线程不安全的原因和处理方式

线程不安全的五个原因
1)线程的随机调度和抢占式执行(根本原因)。
2)修改共享数据。
3)关键代码或指令不是“原子”的(直接原因)。
4)内存可见性。
5)指令重排序。

3.1 线程的随机调度和抢占式执行

原因和处理方式:

原因操作系统上的线程是“随机调度”和“抢占式执行”的,这是产生线程安全问题的根本原因。
处理方式这个原因是由操作系统本身决定的,无法改变。

3.2 修改共享数据

前置知识点:

上述代码中,“count++;”这一句代码实际上是由三条系统指令完成的。包括以下三条指令:
load(读取):从内存中读取数据;
add(运算):进行数据运算;
save(写入):将运算后的数据写入内存中;

根据下图进行分析:

在示例代码中,有多个线程在“同一时刻”,访问了同一变量(count),这个变量就是一个“共享数据”。

通过以上分析,我们发现可以使用两个不同的变量自增,自增完成后再相加,可以解决这里的线程不安全问题。

原因和处理方式:

原因多个线程修改同一个变量。
处理方式这是一个与代码结果相关的问题,有时可以通过调整代码结构解决,但有时代码结构是无法调整的。

3.3 关键代码或指令不是“原子”的

根据下图进行分析:

原因和处理方式:

原因代码中影响线程安全的关键代码或指令不是“原子”的。这是产生线程不安全的直接原因。
处理方式使用 synchronized 关键字加锁,是代码或指令实现逻辑上的原子性,即当线程在执行这些代码或指令时,要么全部不执行,要么全部执行完毕后其他线程才能进入。

应该注意,这里的加锁并不是让代码或指令实现真正的原子性。

也就是说不是真的在执行加锁的代码或指令时不让线程被调度走,而是线程仍可以“暂时离开”。

但此时其他线程也不得进入这段被执行了“一半”的加锁代码或指令。

如上文所讲的,是“打包”为一个逻辑上的整体。

阅读指针 -> 《 synchronized 关键字 和 锁机制 》

<JavaEE> synchronized关键字和锁机制 -- 锁的特点、锁的使用、锁竞争和死锁、死锁的解决方法-CSDN博客Java中加锁的方式有很多种,其中使用 synchronized 关键字进行加锁是最常用的。synchronized 是一种监视器锁(monitor lock)。是为了将多个操作“打包”为一个有“原子性”的操作。进行加锁的时候必须先准备好“锁对象”,锁对象可以是任何类型的实例。synchronized 的底层是使用操作系统的 mutex lock 实现的,本质上依然是调用系统的 API ,依靠 CPU 的特定指令完成加锁功能的。https://blog.csdn.net/zzy734437202/article/details/134742168

3.4 内存可见性和指令重排序

原因:内存可见性指的是一个线程对共享数据的修改,能否即使被其他线程观测到。如果没有,那么其他线程则无法获得正确数据。
原因:

指令重排序是指在“保证程序执行逻辑不变”的情况下,改变指令的执行顺序,以提高编译器的运行效率。

指令重排序在单线程下非常有效,但在多线程下对于“保证程序执行逻辑不变”这一条件判断难度高,因此会出现因为指令重排序而导致的线程不安全。

处理方式:

以上的两个产生线程不安全的原因都来自于编译器优化,本意是通过调整指令执行顺序,提高程序效率,但在多线程环境下,会导致线程不安全。

为应对这种情况,Java提供了 volatile 关键字,用于强制关闭编译器优化。

volatile 关键字的两大核心功能就是保证内存可见性和进制指令重排序。

阅读指针 -> 《 volatile 关键字的 功能 和 使用 》

<JavaEE> volatile关键字 -- 保证内存可见性、禁止指令重排序-CSDN博客简单介绍什么是内存可见性和指令重排序。volatile关键字可以将这两种编译器优化强制关闭。https://blog.csdn.net/zzy734437202/article/details/134757070


四、Java标准库自带的线程安全类

        Java标准库中,有很多类虽然涉及多线程修改共享数据,但又没有加锁措施,因此他们都是线程不安全的。

线程不安全的类:

ArrayList
LinkedList
HashMap
TreeMap
HashSet
TreeSet
StringBuiler

当然,也有一些线程安全的类。

以下的类通过锁机制使得在多线程下,产生线程安全问题的概率大大降低了。

线程安全的类:Vector
HashTable
ConcurrentHashMap
StringBuffer
可以看到有一些类被添加了删除线。是的,根据官方文档,推荐不再使用这几个类,因为它们即将被标准库弃用。
String字符串类比较特殊,因为String本身就是不可变的,因此它就是线程安全的。

阅读指针 -> 《 多线程编程中的常用方法 wait 和 notify 》

链接生成中......

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

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

相关文章

无人机助力电力设备螺母缺销智能检测识别,python基于YOLOv7开发构建电力设备螺母缺销小目标检测识别系统

传统作业场景下电力设备的运维和维护都是人工来完成的&#xff0c;随着现代技术科技手段的不断发展&#xff0c;基于无人机航拍飞行的自动智能化电力设备问题检测成为了一种可行的手段&#xff0c;本文的核心内容就是基于YOLOv7来开发构建电力设备螺母缺销检测识别系统&#xf…

spark的安装与使用:一键自动安装

使用shell脚本一键下载、安装、配置spark&#xff08;单机版&#xff09; 1. 把下面的脚本复制保存为/tmp/install_spark.sh文件 #!/bin/bash# sprak安装版本 sprak_version"2.4.0" # sprak安装目录 sprak_installDir"/opt/module" # hadoop安装路径&…

【数组和函数实战: 斗地主游戏】

目录 1. 玩法说明 2. 分析和设计 3. 代码实现 4. 游戏演示1. 玩法说明 一副54张牌,3最小,两个王最大,其实是2,和上面一样从大到小排列 2. 分析和设计 2.1 分析和设计 常量和变量设计 一副牌有54张,有牌的数值和花色,可以分别用两个数组来存储,card为卡牌表示的数值,color为…

Java数据结构之《希尔排序》题目

一、前言&#xff1a; 这是怀化学院的&#xff1a;Java数据结构中的一道难度中等的一道编程题(此方法为博主自己研究&#xff0c;问题基本解决&#xff0c;若有bug欢迎下方评论提出意见&#xff0c;我会第一时间改进代码&#xff0c;谢谢&#xff01;) 后面其他编程题只要我写完…

CGAL的四叉树、八叉树、正交树

四叉树&#xff08;Quadtree&#xff09;&#xff1a;四叉树是一种用于二维空间分割的数据结构。它将一个二维区域划分为四个象限&#xff0c;每个象限进一步细分为四个小块&#xff0c;以此类推。四叉树可以用于空间索引、图形学、地理信息系统&#xff08;GIS&#xff09;等领…

前端打包添加前缀

vue2添加前缀 router的base加上前缀 export default new Router({mode: history, // 去掉url中的#base: privateDeployUrl, // 这里加上前缀scrollBehavior: () > ({y: 0}),routes: constantRoutes })vue.config.js&#xff0c;publicPath属性加上前缀 publicPath: proces…

Kali 修改IP地址和DNS 开启SSH和远程桌面

一、修和IP和DNS 1、打开配置文件 vim /etc/network/interfaces# 加入 auto eth0 iface eth0 inet static address 10.3.0.231 netmask 255.255.255.0 gateway 10.3.0.12、清空网卡配置 ip addr flush dev eth0 3、配置DNS vim /etc/resolv.confnameserver 114.114.114.11…

爬虫-xpath篇

1.xpath的基础语法 表达式描述nodename选中该元素/从根节点选取、或者是元素和元素间的过渡//从匹配选择的当前节点选择文档中的节点&#xff0c;而不考虑它们的位置.选取当前节点…选取当前节点的父节点选取属性text()选取文本 举例&#xff1a; 路径表达式结果html选择html元…

TLS协议握手流程

浅析 TLS&#xff08;ECDHE&#xff09;协议的握手流程&#xff08;图解&#xff09; - 知乎 前言 通过 wireshark 抓取 HTTPS 包&#xff0c;理解 TLS 1.2 安全通信协议的握手流程。 重点理解几个点&#xff1a; TLS 握手流程&#xff1a;通过 wireshark 抓取 HTTPS 包理解…

常用数据预处理方法 python

常用数据预处理方法 数据清洗缺失值处理示例删除缺失值插值法填充缺失值 异常值处理示例删除异常值替换异常值 数据类型转换示例数据类型转换在数据清洗过程中非常常见 重复值处理示例处理重复值是数据清洗的重要步骤 数据转换示例 数据集成示例数据集成是将多个数据源合并为一…

【网络协议】聊聊网络ReadTimeout和ConnectTimeout

在实际的开发中&#xff0c;网络超时是一个比较常见的问题&#xff0c;比如说针对支付系统&#xff0c;超时就需要进行和三方人员进行核对订单状态&#xff0c;是否人工介入处理。 但其实在设计网络框架的时候&#xff0c;一般都有两个超时参数 连接超时参数 ConnectTimeout&am…

vue项目node-sass^4.14.1 python gyp 报错解决办法

npm i node-sass4.14.1 --sass_binary_sitehttps://npm.taobao.org/mirrors/node-sass/参考链接&#xff1a;链接

LabVIEW在不同操作系统上使VI、可执行文件或安装程序

LabVIEW在不同操作系统上使VI、可执行文件或安装程序 LabVIEW可以在多个操作系统上运行&#xff0c;主要支持以下几种操作系统&#xff1a; Windows&#xff1a; LabVIEW在各个版本的Windows操作系统上都能运行&#xff0c;包括Windows 7、Windows 8和Windows10。LabVIEW为Wi…

elk:filebeat也是一个日志收集工具

filebeat是一个轻量级的日志收集工具&#xff0c;所使用的系统资源比logstash部署和启动使用的资源要小的多 filebeat可以允许在非java环境&#xff0c;他可以代替logstash在非java环境上收集日志 filebeat无法实现数据的过滤&#xff0c;一般是结合logstash的数据过滤功能一…

Safe and Practical GPU Computation in TrustZone论文阅读笔记

Safe and Practical GPU Computation in TrustZone 背景知识&#xff1a; youtube GR视频讲解链接&#xff1a;ASPLOS’22 - Session 2A - GPUReplay: A 50-KB GPU Stack for Client ML - YouTube GPU软件栈&#xff1a; 概念&#xff1a;"GPU软件栈"指的是与GPU硬件…

使用mybatis-plus框架:@Autowired报错Could not autowire. No beans of ‘XXX‘ type found

使用mybatis-plus框架,使用xxmapper报错&#xff1a; 解决办法是&#xff1a;在mapper中添加注解&#xff1a; Repository Mapper 也可以使用 AutowiredSysRoleMenuService sysRoleMenuService;替代 AutowiredSysRoleMenuMapper sysRoleMenuMapper;方法名不同&#xff0c;但…

处理和分析人类语言数据-NLTK安装和使用

简介&#xff1a;NLTK&#xff08;Natural Language Toolkit&#xff09;是一个强大的Python库&#xff0c;用于处理和分析人类语言数据&#xff0c;是一个开源的项目&#xff0c;包含&#xff1a;Python模块&#xff0c;数据集和教程&#xff0c;用于NLP的研究和开发&#xff…

Windows系统下Elasticsearch-7.15.2安装

一、环境 此次笔记使用的运行环境以及软件版本 系统:WIN10 JDK版本&#xff1a;1.8 Elasticsearch版本&#xff1a;7.15.2 elasticsearch-head版本&#xff1a;最新 IK分词器版本&#xff1a;7.15.2 Kibana版本&#xff1a;7.15.2 二、Elasticsearch基本知识 2.1 介绍…

java源码-类与对象

1、类与对象的初步认知 在了解类和对象之前我们先了解一下什么是面向过程和面向对象。 1&#xff09;面向过程编程&#xff1a; C语言就是面向过程编程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。 2&#xff09;面向对…

第一类瑞利索末菲标量衍射模型的方孔衍射的空间像计算(附python计算代码)

记第一类瑞利索末菲标量衍射模型的方孔衍射的空间像计算(附python计算代码) RS type 1 衍射空间像计算傅里叶变换采样条件实际计算计算要求傅立叶变换法计算直接卷积方法计算代码傅立叶变换方法直接卷积https://zhuanlan.zhihu.com/p/624292239 Goodman, J. W. (2004). Intro…