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

【JavaEE初阶】线程安全问题及解决方法

目录

一、多线程带来的风险-线程安全

1、观察线程不安全

2、线程安全的概念

3、线程不安全的原因

4、解决之前的线程不安全问题 

5、synchronized 关键字 - 监视器锁 monitor lock

5.1 synchronized 的特性

5.2 synchronized 使用示例  

5.3 Java 标准库中的线程安全类 


一、多线程带来的风险-线程安全

1、观察线程不安全

public class ThreadDemo2 {private static long count = 0;public static void main(String[] args) throws InterruptedException{Thread t1 = new Thread(()->{for (int i = 1;i <= 500000;i++) {count++;}});Thread t2 = new Thread(()->{for (long i = 0;i < 500000;i++) {count++;}});t1.start();t2.start();t1.join();t2.join();//存在线程安全问题,输出的结果可能不准确System.out.println("count= "+count);}
}

其运行结果:

明显这个结果和我们的预期是不一样的,这是因为存在线程安全问题。若把count++的操作在一个单线程环境下运行 ,便不会出现这样的问题。下面我们来说一下线程安全问题。

2、线程安全的概念

想给出⼀个线程安全的确切定义是复杂的,但我们可以这样认为:
如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。
线程安全,在单线程环境下和多线程环境下都不会出现问题。

3、线程不安全的原因

  • 线程调度是随机的
这是线程安全问题的根本原因 ;
随机调度使⼀个程序在多线程环境下,执行顺序存在很多的变数;
程序猿必须保证 在任意执行顺序下 , 代码都能正常工作。
  •  修改共享数据

多个线程修改同⼀个变量

上面的线程不安全的代码中,涉及到多个线程针对 count 变量进行修改, 此时这个 count 是⼀个多个线程都能访问到的 "共享数据" 。

  • 原子性  

什么是原子性

我们把⼀段代码想象成⼀个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。

那我们应该如何解决这个问题呢?是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样就保证了这段代码的原子性了。有时也把这个现象叫做同步互斥,表示操作是互相排斥的。

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

比如,刚才我们看到的 count++,其实是由三步操作组成的:
  1. 从内存把数据读到 CPU 寄存器中
  2. 进行数据更新
  3. 把数据写回到内存

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

如果不保证原子性,⼀个线程正在对⼀个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。
这点也和线程的抢占式调度密切相关,如果线程不是 "抢占" 的,就算没有原子性,问题也不⼤。
  •  可见性

 可见性指,⼀个线程对共享变量值的修改,能够及时地被其他线程看到。这里先不过多介绍。

  • 指令重排序 
什么是代码重排序?
假设有⼀段代码是这样的:
  1. 去前台取下 U 盘
  2. 去教室写 10 分钟作业
  3. 去前台取下快递
如果是在单线程情况下,JVM、CPU指令集会对其进行优化,比如,按 1->3->2的方式执行,也是没问题,可以少跑⼀次前台。这种叫做指令重排序

编译器对于指令重排序的前提是 "保持逻辑不发⽣变化". 这⼀点在单线程环境下比较容易判断, 但是在多线程环境下就没那么容易了, 多线程的代码执行复杂程度更高, 编译器很难在编译阶段对代码的执行效果进行预测, 因此激进的重排序很容易导致优化后的逻辑和之前不等价.

重排序是⼀个比较复杂的话题, 涉及到 CPU 以及编译器的⼀些底层⼯作原理, 此处不做过多讨论。 

4、解决之前的线程不安全问题 

 解决之后的代码:

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

这时的结果就一定是1000000,如图:

下面就给大家解释一下,这个线程不安全的问题是如何解决的。

5、synchronized 关键字 - 监视器锁 monitor lock

5.1 synchronized 的特性

1) 互斥  

synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到 同⼀个对象 synchronized 就会 阻塞等待
  • 进入 synchronized 修饰的代码块,相当于 加锁
  • 退出 synchronized 修饰的代码块,相当于 解锁

synchronized用的“锁”是存在Java对象“头”里面的。

可以粗略理解成, 每个对象在内存中存储的时候, 都存有⼀块内存表示当前的 "锁定" 状态(类似于厕所 的 "有人/无人").
如果当前是 "无人" 状态, 那么就可以使用, 使用时需要设为 "有人" 状态.
如果当前是 "有人" 状态, 那么其他人无法使用, 只能排队

 

理解 "阻塞等待":

针对每⼀把锁, 操作系统内部都维护了⼀个等待队列. 当这个锁被某个线程占有的时候, 其他线程尝试 进行加锁, 就加不上了, 就会阻塞等待, ⼀直等到之前的线程解锁之后, 由操作系统唤醒⼀个新的线程, 再来获取到这个锁。
注意:
  • 上⼀个线程解锁之后, 下⼀个线程并不是立即就能获取到锁. 而是要靠操作系统来 "唤醒". 这也就是操作系统线程调度的⼀部分工作.
  • 假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B 和 C 都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B 比 C 先来的, 但是 B 不⼀定就能获取到锁, 而是和 C 重新竞争, 并不遵守先来后到的规则.

 synchronized的底层是使用操作系统的mutex lock实现的。

2) 可重入

synchronized 同步块对同⼀条线程来说是可重入的,不会出现自己把自己锁死的问题; 

理解 "把自己锁死" :
一个线程没有释放锁, 然后又尝试再次加锁。
// 第一次加锁, 加锁成功
lock();
// 第二次加锁, 锁已经被占用, 阻塞等待.
lock();
按照之前对于锁的设定, 第二次加锁的时候, 就会阻塞等待. 直到第⼀次的锁被释放, 才能获取到第二 个锁. 但是释放第⼀个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想干了, 也就无法进行 解锁操作. 这时候就会死锁。

这样的锁称为 不可重入锁。

Java 中的 synchronized 是 可重入锁, 因此没有上面的问题。

for (int i = 0; i < 50000; i++) {synchronized (locker) {synchronized (locker) {count++;}}
}
在可重入锁的内部, 包含了 "线程持有者" 和 "计数器" 两个信息.
  • 如果某个线程加锁的时候, 发现锁已经被人占用, 但是恰好占用的正是自己, 那么仍然可以继续获取到锁, 并让计数器自增.
  • 解锁的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到)

5.2 synchronized 使用示例  

synchronized 本质上要修改指定对象的 "对象头",从使用度来看,synchronized 也势必要搭配⼀个具体的对象来使用。

 1) 修饰代码块: 明确指定锁哪个对象.

 锁任意对象:

public class SynchronizedDemo {private Object locker = new Object();public void method() {synchronized (locker) {}}
}

锁当前对象:

public class SynchronizedDemo {public void method() {synchronized (this) {}}
}

2) 直接修饰普通方法: 锁的 SynchronizedDemo 对象 

public class SynchronizedDemo {public synchronized void methond() {}
}

3) 修饰静态方法: 锁的 SynchronizedDemo 类的对象 

public class SynchronizedDemo {public synchronized static void method() {}
}
我们重点要理解,synchronized 锁的是什么. 两个线程竞争同⼀把锁, 才会产生阻塞等待.
两个线程分别尝试获取两把不同的锁, 不会产⽣竞争.

5.3 Java 标准库中的线程安全类 

Java 标准库中很多都是线程不安全的. 这些类可能会涉及到多线程修改共享数据, 又没有任何加锁措施:
  • ArrayList
  • LinkedList
  • HashMap
  • TreeMap
  • HashSet
  • TreeSet
  • StringBuilder
但是还有⼀些是线程安全的. 使用了⼀些锁机制来控制:
  • Vector (不推荐使⽤)
  • HashTable (不推荐使⽤)
  • ConcurrentHashMap
  • StringBuffer

StringBuffer 的核心方法都带有 synchronized .  

还有的虽然没有加锁, 但是不涉及 "修改", 仍然是线程安全的: String

相关文章:

【JavaEE初阶】线程安全问题及解决方法

目录 一、多线程带来的风险-线程安全 1、观察线程不安全 2、线程安全的概念 3、线程不安全的原因 4、解决之前的线程不安全问题 5、synchronized 关键字 - 监视器锁 monitor lock 5.1 synchronized 的特性 5.2 synchronized 使用示例 5.3 Java 标准库中的线程安全类…...

uniapp高德、百度、腾讯地图配置 SHA1

uniapp高德、百度、腾讯地图配置 SHA1 当winr弹出cmd弹框后输入 keytool -list -v -keystore debug.keystore 显示keytool 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。可以先看看是否有下载jdk且配置了环境变量&#xff0c;具体操作如下&#xff1a;keyto…...

[AutoSAR存储] 车载存储层次 和 常用存储芯片概述

公知及经验整理&#xff0c;原创保护&#xff0c;禁止转载。 专栏 《AutoSAR存储》 <<<< 返回总目录 <<<< 1 存储系统层次 先抛个问题&#xff0c; 为什么要划分存储器的层次&#xff1f; 速度越快&#xff0c;但成本越高&#xff0c;从经济的角度规…...

进程并发-信号量经典例题-吸烟者问题

1 题目描述 吸烟者问题 在一个房间内有三个吸烟者和一个香烟供应者。为了制造并抽掉香烟&#xff0c;每个吸烟者需要三样物品&#xff1a;烟草、纸和火柴&#xff0c;供应者有丰富物品提供。在三个吸烟者中&#xff0c;第一个有自己的烟草&#xff0c;第二个有自己的纸&#…...

[ruby on rails] array、jsonb字段

一、jsonb # 新增 add_column :shi_tis, :setting, :jsonb, default: {}# string转jsonb def changechange_column :users, :setting, :jsonb, using: setting::jsonb, default: {} end# 加索引 add_index :users, :setting, using: :gin # 这样就为setting jsonb字段创建了一…...

Feign接口请求返回异常 no suitable HttpMessageConvert found for response type

问题场景&#xff1a; 后端调用feign接口请求, 接口返回异常, no suitable HttpMessageConvert found for response type 问题描述 报错异常如下&#xff1a; //根据图片特征 去查询人员信息ResultVo<List> personVos ipbdFaceLibPersonApi.queryFacePersonByFeatur…...

【brpc学习实践九】mbvar及bvar可观测

概念、学习地址 mbvar中有两个类&#xff0c;分别是MVariable和MultiDimension&#xff0c;MVariable是多维度统计的基类&#xff0c;MultiDimension是派生模板类。也是主要用来多多线程计数用的。这里用到再详细去了解即可 https://github.com/luozesong/brpc/blob/master/do…...

Vue 3

Vu3 简述: 快速上手,开发即用,具体知识参考官方文档 具备知识 Vite 了解即可,使用时按需配置,更多参考官方文档( https://cn.vitejs.dev) 简述: 极速响应工具 (构建、启动、更新、插件使用等) 核心思想: 依赖 和 源码 工作原理: 引入rollup: 灵活,相比webpack速度快,…...

GitHub Copilot 替代品?

应该没人不知道代码补全这个东西了吧&#xff0c;第一次使用 GitHub Copilot 之后&#xff0c;只觉得真香&#xff0c;现在居然还有一点离不了了。后面因为收费原因&#xff0c;就没再用了&#xff0c;找了一个 tabnine 替代&#xff0c;用了几天&#xff0c;体验是真的比不上 …...

设计循环队列(详解)

呀哈喽&#xff0c;我是结衣 今天给大家带来的内容如标题所述&#xff0c;我们来设计环形队列&#xff0c;虽然队列没有讲&#xff0c;但是我就是想讲啊。那么环形队列现在开始。 队列的属性 在设计环形队列前&#xff0c;我们先要了解队列的特点&#xff08;先进先出&#x…...

【Python】Vscode解决Python中制表符和空格混用导致的缩进问题

【Python】Vscode解决Python中制表符和空格混用导致的缩进问题 文章目录 【Python】Vscode解决Python中制表符和空格混用导致的缩进问题1. 问题来源2. 解决Reference 1. 问题来源 在python中使用缩进来进行代码块的分区&#xff0c;通常来说python的一个缩进包含4个空格&#…...

CocosCreator 面试题(十六)Cocos Creator 节点池的基本原理是什么?如何使用?

一、Cocos Creator 节点池的基本原理是什么&#xff1f; Cocos Creator 是一个游戏开发引擎&#xff0c;它提供了节点池&#xff08;Node Pool&#xff09;的功能&#xff0c;用于管理和重用游戏中的节点对象。节点池的基本原理如下&#xff1a; 创建初始节点&#xff1a;在游戏…...

VUE留言板

效果预览图 完整代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>作业</title><styl…...

【办公软件】电脑开机密码忘记了如何重置?

这个案例是家人的电脑&#xff0c;已经使用多年&#xff0c;又是有小孩操作过的&#xff0c;所以电脑密码根本不记得是什么了&#xff1f;那难道这台电脑就废了吗&#xff1f;需要重新装机吗&#xff1f;那里面的资料不是没有了&#xff1f; 为了解决以上问题&#xff0c;一般…...

PTA NeuDS-数据库题目集

一.判断题 1.在数据库中产生数据不一致的根本原因是冗余。T 解析&#xff1a;数据冗余是数据库中产生数据不一致的根本原因&#xff0c;因为当同一数据存储在多个位置时&#xff0c;如果其中一个位置的数据被修改&#xff0c;其他位置的数据就不一致了。因此&#xff0c;在数据…...

Redis深入理解-内核请求处理流程、数据传输协议

Redis 内核级请求处理流程 Redis Server 其实就是 Linux 服务器中的一个进程 主要还是下图的流程 应用先和 server 端建立 TCP 连接建立连接之后&#xff0c;server 端就会有一个与该客户端通信的 socket&#xff0c;客户端的读写请求发送到服务端的 socket那么通过 IO 多路…...

Mac电脑卸载/删除nodejs

使用命令行卸载 Node.js 第一步&#xff1a;打开终端&#xff0c;输入以下命令显示 Node.js 的安装路径&#xff1a; which node执行该命令后&#xff0c;会显示安装路径&#xff1a; /usr/local/bin/node第二步&#xff1a;输入以下命令删除 Node.js 相关的文件&#xff1a;…...

C语言之内存函数

C语言之内存函数 文章目录 C语言之内存函数1. memcpy 使⽤和模拟实现1.1 memcpy 函数的使用1.3 memcpy的模拟实现 2. memmove 使⽤和模拟实现2.1 memmove 函数的使用2.2 memmove的模拟实现 3. memset 函数的使用4. memcmp 函数的使⽤ 1. memcpy 使⽤和模拟实现 函数声明如下&a…...

基本数据结构二叉树(1)

目录 1.树概念及结构 1.1树的概念 1.2 树的相关概念 1.3 树的表示 1.4 树在实际中的运用&#xff08;表示文件系统的目录树结构&#xff09; 2.二叉树概念及结构 2.1概念 2.2现实中的二叉树&#xff1a; 2.3 特殊的二叉树&#xff1a; 2.5 二叉树的存储结构 2. 链式存…...

【python】Python将100个PDF文件对应的json文件存储到MySql数据库(源码)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析

今天聊的内容&#xff0c;我认为是AI开发里面非常重要的内容。它在AI开发里无处不在&#xff0c;当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗"&#xff0c;或者让翻译模型 "将这段合同翻译成商务日语" 时&#xff0c;输入的这句话就是 Prompt。…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

visual studio 2022更改主题为深色

visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中&#xff0c;选择 环境 -> 常规 &#xff0c;将其中的颜色主题改成深色 点击确定&#xff0c;更改完成...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名&#xff08;Class Name)2.协议名&#xff08;Protocol Name)3.方法名&#xff08;Method Name)4.属性名&#xff08;Property Name&#xff09;5.局部变量/实例变量&#xff08;Local / Instance Variables&…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Angular微前端架构:Module Federation + ngx-build-plus (Webpack)

以下是一个完整的 Angular 微前端示例&#xff0c;其中使用的是 Module Federation 和 npx-build-plus 实现了主应用&#xff08;Shell&#xff09;与子应用&#xff08;Remote&#xff09;的集成。 &#x1f6e0;️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...