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

JavaEE——线程安全

目录

  • 前言
  • 1.线程安全的定义
  • 2.线程安全问题产生的原因
    • 2.1 多个线程修改一个变量
    • 2.2 修改操作不是原子的
    • 2.3 内存可见性引起的线程安全问题
  • 3.解决线程安全问题的方法
    • 3.1 通过synchronized关键字加锁
    • 3.2 使用volatile关键字
  • 总结

前言

在使用多线程的时候,难免会出现一些问题,会产生线程安全问题,本篇文章就来简单讲述一下线程安全问题产生的原因和对应的解决方案。

1.线程安全的定义

线程安全是指当多个线程同时访问某个对象或方法时,不会出现数据不一致、逻辑错误或者意外结果的情况。即当把一个单线程执行的程序修改成一个多线程程序,产生的结果要和原来一样,如果不一样,则可以认为出现了线程安全问题。

2.线程安全问题产生的原因

造成线程不安全有各种各样的原因,但是导致线程不安全的根本原因就是线程的调度是随机的。下面还有一些常见的原因。

2.1 多个线程修改一个变量

多个线程同时访问共享资源(比如说同一个变量)可能会产生竞争状态,比如一个线程读取的是1,然后改成2,还没有保存,另一个线程也来读取,此时读取的还是1,这就会导致每次多线程执行完毕的时候,得出的结果可能都不一样。

2.2 修改操作不是原子的

就比如说对变量进行++操作,在java语句中是一行代码,实际上是三个操作:读取内存数据到CPU;对数据进行更新;然后将数据写回CPU。
在进行多线程操作的时候,可能就会因为操作顺序的抢占而产生线程安全问题

2.3 内存可见性引起的线程安全问题

下面先给出一段代码:

public class Demo17 {private static int flag = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while(flag == 0){//do nothing}System.out.println("t1线程结束");});Thread t2 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("请输入一个数字:");flag = scanner.nextInt();System.out.println("t2线程结束");});t1.start();t2.start();t1.join();t2.join();}
}

我们来尝试运行一下
我们输入一个非零数字,理论上应该弹出t1线程结束和t2线程结束,我们来看结果:
在这里插入图片描述
我们发现并没有显示预期的结果,这是为什么?
这里就是由于”内存可见性“。
意思就是这里t2对flag进行修改,但是t1没有感知到,就是t1”没有看见“,一直认为flag还是0。
这就是内存可见性问题。
这是由于编译器优化所产生的问题,在代码编译时,编译器会进行一些优化策略,对代码进行优化,来提高程序的效率,这里是由于编译器优化出现了误判,从而导致代码的逻辑发生了改变,上面的程序就是由于此原因而出现问题,在t1的反复循环中,flag会被存进寄存器里来提高读取效率,不通过内存来读,而t2把通过内存把flag的数值变化后,t1仍然读的是原来存取到寄存器中的‘0’的值,这就是t1线程“看不见”flag的值变化的原因。

3.解决线程安全问题的方法

上述的根本原因不好解决,我们可以通过把修改操作变成”原子的“ 来解决线程安全问题

3.1 通过synchronized关键字加锁

可以通过加锁的方式将多段代码变成一个整体,让其它的线程无法进行干扰。我们通过synchronized关键字来实现锁。
比如我们对如下代码进行分析:

public class Demo14 {public 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);}
}

可以看出这里的操作并不是原子的,所以并不能如愿获得100000的结果。
我们就需要使用synchronized关键字来进行加锁,方法如下:

package thread;/*** @author Wind* @date 2025-04-13*/
public class Demo14 {public static int count = 0;private static Object locker = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {synchronized (locker) {count++;}}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {synchronized (locker){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count);}
}

将count++操作进行加锁,在执行count++前,后台会将语句lock,在调用完count++操作后,进行unlock操作,假设当t1加锁后,t2要进行count++前,要执行加锁,此时加锁不成功,会阻塞等待,知道t1进行unlock,这样就不会导致多个线程对同一个变量进行修改。现在观看结果,可以发现结果正确:
在这里插入图片描述
synchronized还可以用来修饰方法,同时也能起到加锁的作用:

public class Demo14 {static class Counter{private int count = 0;private Object locker = new Object();synchronized public void add() {count++;}}public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.add();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.add();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + counter.count);}
}

这里实际上就是对add方法加锁。

synchronized也可以对静态方法加锁,此时可以认为是对类对象进行加锁。

3.2 使用volatile关键字

使用volatile关键字可以解决上面所出现的内存可见性问题。
volatile关键字用于修饰变量,用于告诉编译器,这个变量是经常性变化的,使编译器不对变量进行上面内存可见性中所出现的优化。
修改后的代码(将flag用volatile修饰):

public class Demo17 {private static volatile int flag = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while(flag == 0){//do nothing}System.out.println("t1线程结束");});Thread t2 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("请输入一个数字:");flag = scanner.nextInt();System.out.println("t2线程结束");});t1.start();t2.start();t1.join();t2.join();}
}

下面看运行结果:
在这里插入图片描述
可以看到,程序正常结束。
此外还需要注意的是,volatile虽然可以解决内存可见性问题,但它本身不具有原子性,无法解决不是原子性的问题。

总结

以上就是对线程安全的简单介绍,希望这篇文章能帮助你更加熟练的运用多线程。

相关文章:

JavaEE——线程安全

目录 前言1.线程安全的定义2.线程安全问题产生的原因2.1 多个线程修改一个变量2.2 修改操作不是原子的2.3 内存可见性引起的线程安全问题 3.解决线程安全问题的方法3.1 通过synchronized关键字加锁3.2 使用volatile关键字 总结 前言 在使用多线程的时候&#xff0c;难免会出现…...

Redis Hash 介绍

Redis Hash 介绍 从基础命令、内部编码和使用场景三个维度分析如下&#xff1a; 一、基础命令 Redis Hash 提供了丰富的操作命令&#xff0c;适用于字段&#xff08;field&#xff09;级别的增删改查&#xff1a; 设置与修改 HSET&#xff1a;设置单个字段值&#xff08;HSET…...

[redis进阶一]redis的持久化(2)AOF篇章

目录 一 为什么有了RDB持久化机制还要有AOF呢 板书介绍具体原因: ​编辑二 详细讲解AOF机制 (1)AOF的基本使用 1)板书如下 2)开启AOF机制: 3) AOF工作流程 (2)AOF是否会影响到redis性能 ​编辑 (3)AOF缓冲区刷新策略 (4)AOF的重写机制 板书如下: 为什么要有这个重写机…...

【Linux我做主】探秘gcc/g++和动静态库

TOC Linux编译器gcc/g的使用 github地址 有梦想的电信狗 前言 在软件开发的世界中&#xff0c;编译器如同匠人的工具&#xff0c;将人类可读的代码转化为机器执行的指令。 对于Linux开发者而言&#xff0c;gcc和g是构建C/C程序的核心工具链&#xff0c;掌握它们的原理和使…...

Linux `init 0` 相关命令的完整使用指南

Linux init 0 相关命令的完整使用指南—目录 一、init 系统简介二、init 0 的含义与作用三、不同 Init 系统下的 init 0 行为1. SysVinit&#xff08;如 CentOS 6、Debian 7&#xff09;2. systemd&#xff08;如 CentOS 7、Ubuntu 16.04&#xff09;3. Upstart&#xff08;如 …...

【英语语法】基本句型

目录 前言一&#xff1a;主谓二&#xff1a;主谓宾三&#xff1a;主系表四&#xff1a;主谓双宾五&#xff1a;主谓宾补 前言 英语基本句型是语法体系的基石&#xff0c;以下是英语五大基本句型。 一&#xff1a;主谓 结构&#xff1a;主语 不及物动词 例句&#xff1a; T…...

Vue3中发送请求时,如何解决重复请求发送问题?

文章目录 前言一、问题演示二、使用步骤1.One组件2.Two组件封装工具函数处理请求 总结 前言 在开发过程中&#xff0c;重复请求发送问题可能会导致数据不一致、服务器压力增加或用户操作异常。以下是解决重复请求问题的常见方法和最佳实践&#xff1a; 一、问题演示 我们看着…...

信息学奥赛一本通 1622:Goldbach’s Conjecture | 洛谷 UVA543 Goldbach‘s Conjecture

【题目链接】 ybt 1622&#xff1a;Goldbach’s Conjecture 洛谷 UVA543 Goldbach’s Conjecture 【题目考点】 1. 筛法求质数表 埃筛线性筛&#xff08;欧拉筛&#xff09; 知识点讲解见信息学奥赛一本通 2040&#xff1a;【例5.7】筛选法找质数 【解题思路】 首先使用埃…...

在极狐GitLab 身份验证中如何使用 OIDC?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 使用 OpenID Connect 作为认证提供者 (BASIC SELF) 您可以使用极狐GitLab 作为客户端应用程序&#xff0c;与 OpenID Connec…...

计算机视觉与深度学习 | 基于YOLOv8与光流法的目标检测与跟踪(Python代码)

===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 目标检测与跟踪 关键实现逻辑检测-跟踪协作机制‌特征点选择策略‌运动…...

解决 VSCode 中 NVM 配置后无法识别 Node 和 NPM 的问题

在开发中&#xff0c;我们经常需要使用 Node.js 和 NPM 来管理 JavaScript 项目依赖&#xff0c;而 NVM&#xff08;Node Version Manager&#xff09;是开发者在本地环境中管理多个 Node.js 版本的得力工具。不过&#xff0c;有时候在 VSCode 中配置完 NVM 后&#xff0c;可能…...

观察者模式:从博客订阅到消息队列的解耦实践

观察者模式&#xff1a;从博客订阅到消息队列的解耦实践 一、模式核心&#xff1a;用事件驱动实现对象间松耦合 在新闻 APP 中&#xff0c;当热点事件发生时需要实时通知所有订阅用户&#xff1b;在电商系统中&#xff0c;库存变化需触发价格监控模块重新计算。这类场景的核心…...

ReportLab 导出 PDF(页面布局)

ReportLab 导出 PDF&#xff08;文档创建&#xff09; ReportLab 导出 PDF&#xff08;页面布局&#xff09; ReportLab 导出 PDF&#xff08;图文表格) PLATYPUS - 页面布局和排版 1. 设计目标2. 开始3. Flowables3.1. Flowable.draw()3.2. Flowable.drawOn(canvas,x,y)3.3. F…...

qt与html通信

**Cef视图&#xff08;CefView&#xff09;**是指在使用Chromium Embedded Framework&#xff08;CEF&#xff09;时&#xff0c;嵌入到应用程序中的浏览器视图。CEF是一个开源项目&#xff0c;它基于Google的Chromium浏览器&#xff0c;允许开发者将Web浏览器功能嵌入到自己的…...

git 根据http url设置账号密码

1. 原因 场景&#xff1a;有一种情况&#xff0c;比如在github上面有多个账号&#xff0c;并且每个账号都有些仓库的内容需要修改&#xff0c;并且这些账号自己&#xff0c;不是协作者的关系。这个时候需要针对每个仓库的url设置用户名密码, 2. 设置 2.1 第一步&#xff1a;…...

【CVE-2024-10929】ARM CPU漏洞安全通告

安全之安全(security)博客目录导读 目录 一、概述 二、CVE详情 三、受影响产品 四、建议措施 五、致谢 六、版本历史 一、概述 在部分基于Arm架构的CPU中发现了一个潜在安全问题&#xff0c;称为Spectre-BSE&#xff08;Branch Status Eviction&#xff0c;分支状态驱逐…...

OpenCV 图形API(33)图像滤波-----高斯模糊函数gaussianBlur()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 使用高斯滤波器对图像进行模糊处理。 该函数使用指定的高斯核对源图像进行滤波。输出图像必须与输入图像具有相同的类型和通道数。 cv::gapi::g…...

【Android】 如何将 APK 内置为系统应用(适用于编辑设置属性)

如何将 APK 内置为系统应用(适用于编辑设置属性) 在 Android 中&#xff0c;将 APK 文件内置为系统应用涉及到一系列的命令和步骤。以下是详细的操作流程&#xff0c;帮助您解决常见问题&#xff0c;如 /system not in /proc/mounts 的错误。 挂载system/app获取可读写权限 …...

【2025最新版】火鸟门户v8.5系统源码+PC、H5、小程序 +数据化大屏插件

一.介绍 火鸟地方门户系统V8.5源码 系统包含4端&#xff1a; PCH5小程序APP 二.搭建环境 系统环境&#xff1a;CentOS、 运行环境&#xff1a;宝塔 Linux 网站环境&#xff1a;Nginx 1.2.22 MySQL 5.6 PHP-7.4 常见插件&#xff1a;fileinfo &#xff1b; redis 三.测…...

关于 传感器 的详细解析,涵盖定义、分类、工作原理、常见类型、应用领域、技术挑战及未来趋势,结合实例帮助理解其核心概念

以下是关于 传感器 的详细解析&#xff0c;涵盖定义、分类、工作原理、常见类型、应用领域、技术挑战及未来趋势&#xff0c;结合实例帮助理解其核心概念&#xff1a; 一、传感器的定义与核心功能 1. 定义 传感器&#xff08;Sensor&#xff09;是一种能够将物理量&#xff…...

EtherCAT转ProfiNet边缘计算网关配置优化:汽车制造场景下PLC与机器人协同作业案例

1.行业背景与需求分析 智能汽车焊装车间是汽车制造的核心工艺环节&#xff0c;某德国豪华品牌在其上海MEB工厂新建的焊装车间中&#xff0c;采用西门子S7-1500PLC作为ProfiNet主站&#xff0c;负责整线协调与质量追溯&#xff1b;同时部署KUKAKR1500Titan机器人&#xff08;Eth…...

极狐GitLab CI/CD 流水线计算分钟数如何管理?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 计算分钟管理 (PREMIUM SELF) 在极狐GitLab 16.1 中&#xff0c;从 CI/CD 分钟数重命名为计算配额或计算分钟数。 管理员可…...

HTTP协议 --- 超文本传输协议 和 TCP --- 传输控制协议

是基于 TCP 协议的 80 端口的一种 C/S 架构协议。 特点&#xff1a;无状态 --- 数据传输完成后&#xff0c;会断开 TCP 连接&#xff0c;哪怕浏览器还正常运行。 请求报文 --- 方法 响应报文 --- 状态码 是一种面向连接的可靠传输协议 。 面向连接 --- 在传输数据之前&am…...

类和对象(下篇)(详解)

【本节目标】 1. 再谈构造函数 2. Static成员 3. 友元 4. 内部类 5. 再次理解封装 1. 再谈构造函数 1.1 构造函数体赋值 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值。 #include <iostream> using name…...

Uniapp:获取当前定位坐标

目录 一、出现场景二、具体使用 一、出现场景 在项目的开发中&#xff0c;会出现打卡、定位当前位置的功能&#xff0c;那我们如何获取当前位置呢&#xff1f;这就需要使用getLocation来获取当前位置坐标 二、具体使用 uni.getLocation({type: wgs84, // 返回可以用于uni.op…...

最大子序和问题——动态规划/贪心算法解决

目录 一&#xff1a;问题描述 二&#xff1a;解决思路1——动态规划思想 三&#xff1a;C 语言代码实现 四&#xff1a;复杂度分析 五&#xff1a;解决思路2——贪心算法思想 六&#xff1a;具体步骤 七: C语言代码实现 八&#xff1a;复杂度分析 一&#xff1a;问题描述 …...

【Unity】JSON数据的存取

这段代码的结构是为了实现 数据的封装和管理&#xff0c;特别是在 Unity 中保存和加载玩家数据时。以下是对代码设计的逐步解释&#xff1a; 1. PlayerCoin 类 PlayerCoin 是一个简单的数据类&#xff0c;用于表示单个玩家的硬币信息。它包含以下字段&#xff1a; count&…...

LeetCode【剑指offer】系列(位运算篇)

剑指offer15.二进制中1的个数 题目链接 题目&#xff1a;编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位数为 ‘1’ 的个数&#xff08;也被称为 汉明重量).&#xff09;。 思路一&#xff…...

unity socket 客户端和c#服务器通信

服务器 using BarrageGrab; using System; using System.Collections.Concurrent; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading;namespace Lyx {class Server{private TcpListener listener;private Concurre…...

如何在Vue中实现取消聚焦el-select——从零到部署的完整指南

如何在Vue中实现取消聚焦el-select——从零到部署的完整指南 在开发Vue项目时&#xff0c;经常会遇到需要处理用户交互和组件状态管理的情况。特别是在使用Element UI组件库时&#xff0c;如何优雅地管理组件的状态显得尤为重要。本文将详细介绍如何在取消对话框时自动取消el-s…...