java多线程编程(二)一一>线程安全问题, 单例模式, 解决程线程安全问题的措施

引言: 

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的


线程安全问题的原因:

一.操作系统的随机调度 :

二.多个线程修改同一个变量: 

三.修改操作不是原子的 :

四.内存可见性 :

五.指令重排序:  

解决上述的线程安全问题的措施:   



线程安全问题的原因:

 一.操作系统的随机调度 :  

1. 这是线程安全问题的 罪魁祸首 随机调度使⼀个程序在多线程环境下, 执行顺序存在很多的变数.

例子:这个代码返回结果就是随机调度的体现 

 现象: 

class MyThread extends Thread {@Overridepublic void run() {while (true) {System.out.println("这⾥是t线程运⾏的代码");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class Demo1 {public static void main(String[] args) throws InterruptedException {MyThread t = new MyThread();t.start();while (true) {System.out.println("这里是主线程");Thread.sleep(1000);}}
}

现象: 


二.多个线程修改同一个变量: 

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

例子:下面这个代码应该预期应该自增10w次,但是由于线程安全问题,达不到预期 
public class Demo11 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count++;}System.out.println("t1 结束");});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count++;}System.out.println("t2 结束");});t1.start();t2.start();t1.join();t2.join();// 一个线程自增 5w 次, 两个线程, 总共自增 10w 次. 预期结果, count = 10wSystem.out.println(count);}
}

三.修改操作不是原子的 : 

这里我们count++,时候站在操作系统层面,我们要进行大致三步:

load:把count的值读到寄存器里 

add: 把寄存器中的内容加1

save:  把寄存器写回内存  

进行以上以上操作时候由于操作系统随机调度多个线程之间,可能出现数据被覆盖的情况,这就是操作不原子的体现: 


四.内存可见性 : 

这个问题可以引入Java内存模型说明: 

线程之间的共享变量存在 主内存 (Main Memory).(主内存就是泛指的内存)
每⼀个线程都有自己的 "工作内存" (Working Memory) .(工作内存指的是CPU 的寄存器和高速缓存L1,L2,L3)
当线程要读取一个共享变量的时候, 会先把变量从主内存拷贝到工作内存(CPU 的寄存器), 再从工作内存(CPU 的寄存器),读取数据。 
其实是通过jvm和编译器来实现优化,把读内存优化为读寄存器了,有时候这个优化逻辑不符合我们的预期的逻辑出现细节上的偏差,就导致内存可见性问题

代码实例:一个线程读取一个线程修改
这个循环条件的flag判断是条件跳转指令cmp是寄存器操作会很快,while会循环很多次,jvm觉得每次觉得读到的都是0,直接就把 读内存优化为读寄存器了, 此时寄存器的值为0,此时用户输入 1 想结束线程时,t1线程读不到这个在内存中(主内存)的值,所以这个t1线程结束不了
public static void main(String[] args) {Thread t1 = new Thread(()->{while (flag == 0){}System.out.println("t1线程结束");});Thread t2 = new Thread(()->{Scanner in = new Scanner(System.in);System.out.println("请输入flag的值");flag = in.nextInt();});t1.start();t2.start();}


五.指令重排序:

要说清楚这个问题就要引入一种设计模式:单例模式

 单例模式: 

单例模式能保证某个类在程序中只存在唯⼀⼀份实例, 而不会创建出多个实例,不能创建多个对象,这里有两种写法:饿汉模式和懒汉模式: 

1.饿汉模式:  类加载的同时, 创建实例:  
类加载时就new对象所以成为饿汉模式,注意构造方法私有化,防止类外多次实例化。
​
class Singleton {private static Singleton instance = new Singleton();//类加载时就new对象//构造方法私有化,防止类外被实例化多个对象private Singleton() {}public static Singleton getInstance() {return instance;}
}​

2.懒汉模式-单线程版:  

懒汉模式,能不实例化就不实例化所以的懒汉, 第⼀次使用的时候才创建实例

class Singleton {private static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

 但是这个在多线程下还是存在线程安全问题的,而且还有一个指令重排序问题 。

 就算加锁(结合下面看看),解决了线程安全问题,但是instance = new Singleton();

new对象操作,细分为这三步:

1 .申请内存空间

2.构造对象(初始化)

3.内存空间首地址,赋值给引用变量  
由于指令重排序,可能会改变顺序,顺序可能从1,2,3一一>1,3,2  
在这个代码情况下就可能,在类外调用,getInstance方法拿到未初始化的对象导致线程安全问题。
 
class Singleton {private static Singleton instance = null;private static Object locker = new Object(); private Singleton() {}public synchronized static Singleton getInstance() {if(instance == null) {//进一步优化效率,减少锁的阻塞状态,(instance == null才加锁才new对象)synchronized(locker){if (instance == null) {instance = new Singleton();}}}return instance;}}



解决上述的线程安全问题的措施:   

操作系统的随机调度 是操作系统,计算机一脉传承,不能解决,
接下来我们围绕三~四~五~展开 

三.修改操作不是原子的 : 

这里我们可以把相关的操作打包起来,就是引入锁:

synchronized 关键字 - 监视器锁 monitor lock :

synchronized 会起到互斥效果, 某个线程执⾏到某个对象的 synchronized 中时, 其他线程如果也执行 到同⼀个对象 synchronized 就会阻塞等待.
进⼊ synchronized 修饰的代码块, 相当于 加锁
退出 synchronized 修饰的代码块, 相当于 解锁 
 就和上厕所一样:

理解 "阻塞等待":
针对每⼀把锁, 操作系统内部都维护了⼀个等待队列. 当这个锁被某个线程占有的时候, 其他线程尝试 进行加锁, 就加不上了, 就会阻塞等待, ⼀直等到之前的线程解锁之后, 由操作系统唤醒⼀个新的线程, 再来获取到这个锁。
注意:这里还有一种应用程序级别的忙等,不涉及操作系统

三.的解决代码:

这里注意两个线程要加他一把锁,才有互斥的效果。 

 

 private static int count = 0;public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(() -> {synchronized (locker) {for (int i = 0; i < 50000; i++) {count++;}}System.out.println("t1 结束");});Thread t2 = new Thread(() -> {synchronized (locker) {for (int i = 0; i < 50000; i++) {count++;}}System.out.println("t2 结束");});t1.start();t2.start();t1.join();t2.join();// 一个线程自增 5w 次, 两个线程, 总共自增 10w 次. 预期结果, count = 10wSystem.out.println(count);}
  

四.内存可见性解决:  

这里引入volatile关键字:


1.volatile 能保证每次读取操作都是读内存

2.volatile 能保证变量的读取和修改不会出发指令重排序 

​public class{
public volitile static int flag = 0 //这样修饰变量编译器就不会优化了
public static void main(String[] args) {Thread t1 = new Thread(()->{while (flag == 0){}System.out.println("t1线程结束");});Thread t2 = new Thread(()->{Scanner in = new Scanner(System.in);System.out.println("请输入flag的值");flag = in.nextInt();});t1.start();t2.start();}​}

五.指令重排序:

饿汉模式是没有线程安全问题和指令重排序的,因为都是读操作

懒汉模式下就会有:

我们也加上volatile关键字:

​
class Singleton {private static volatile Singleton instance = null;//加上volatile private static Object locker = new Object(); private Singleton() {}public synchronized static Singleton getInstance() {if(instance == null) {//进一步优化效率,减少锁的阻塞状态,(instance == null才加锁才new对象)synchronized(locker){if (instance == null) {instance = new Singleton();}}}return instance;}}​

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

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

相关文章

推荐一款非常好用的视频编辑软件:Movavi Video Editor Plus

MovaviVideoEditorPlus(视频编辑软件)可以制作令人惊叹的视频&#xff0c;即使您没有任何视频编辑方面的经验! 该款视频编辑程序没有复杂的设置&#xff0c;只需进行直观的拖放控制。在您的电脑上免费使用MovaviVideoEditor亲身体验它的简单易用性与强大功能! 基本简介 您是否…

【算法】递归+深搜+哈希表:889.根据前序和后序遍历构造二叉树

目录 1、题目链接 相似题目: 2、题目 ​3、解法&#xff08;针对无重复值&#xff0c;哈希表递归&#xff09; 函数头-----找出重复子问题 函数体---解决子问题 4、代码 1、题目链接 889.根据前序和后序遍历构造二叉树&#xff08;LeetCode&#xff09; 相似题目: 105.…

高级信号完整性

高级信号完整性&#xff0c;2022年版&#xff0c;1473页&#xff0c;24h秒发 内容庞大&#xff0c;都是新的内容、架构 QS排名100内的美国高校课件 发货内容&#xff1a; 29个分章节PDF 1个汇总PDF&#xff0c;1473页 点击获取 课程首先对电磁学进行了回顾。随后&#xff0c;…

yelp数据集上识别潜在的热门商家

yelp数据集是研究B2C业态的一个很好的数据集&#xff0c;要识别潜在的热门商家是一个多维度的分析过程&#xff0c;涉及用户行为、商家特征和社区结构等多个因素。从yelp数据集里我们可以挖掘到下面信息有助于识别热门商家 用户评分和评论分析 评分均值: 商家的平均评分是反映其…

qt QDataStream详解

1. 概述 QDataStream是Qt框架中的一个核心类&#xff0c;主要用于处理二进制数据的序列化和反序列化。它提供了高效、跨平台的方式&#xff0c;将C数据结构转化为字节流&#xff0c;便于在网络传输、持久化存储等场景下使用。QDataStream可以处理包括整数、浮点数、布尔值、字…

使用Spring Validation实现数据校验详解

目录 前言1. Spring Validation概述2. 配置Spring Validation2.1 引入依赖2.2 启用全局校验 3. 使用注解进行参数校验3.1 基本校验注解3.2 使用Pattern进行正则校验3.3 综合示例 4. 在控制器层应用校验4.1 方法参数校验4.2 自定义错误处理 5. 高级应用&#xff1a;自定义校验注…

链表(C 语言)

目录 一、链表的概念1. 链表的结构2. 链表的分类3. 链表的优势 二、链表的实现1. 无头单项非循环链表的实现1.1 代码说明 2. 带头双向循环链表的实现2.1 代码说明 三、链表和顺序表的区别四、链表总结 一、链表的概念 链表是一种顺序表&#xff0c;它由一个一个的节点组成&…

QinQ的基础实验

拓扑 命令 LSW1 [LSW1]vlan batch 2 3 4 Info: This operation may take a few seconds. Please wait for a moment...done. [LSW1]interface g0/0/1 [LSW1-GigabitEthernet0/0/1]port link-type hybrid [LSW1-GigabitEthernet0/0/1]port hybrid untagged vlan 2 3 [LSW…

python-读写Excel:openpyxl-(4)下拉选项设置

使用openpyxl库的DataValidation对象方法可添加下拉选择列表。 DataValidation参数说明&#xff1a; type&#xff1a; 数据类型("whole", "decimal", "list", "date", "time", "textLength", "custom"…

Elasticsearch中时间字段格式用法详解

Elasticsearch中时间字段格式用法详解 攻城狮Jozz关注IP属地: 北京 2024.03.18 16:27:51字数 758阅读 2,571 Elasticsearch&#xff08;简称ES&#xff09;是一个基于Lucene构建的开源、分布式、RESTful搜索引擎。它提供了全文搜索、结构化搜索以及分析等功能&#xff0c;广泛…

【JavaEE初阶 — 多线程】Thread的常见构造方法&属性

目录 Thread类的属性 1.Thread 的常见构造方法 2.Thread 的几个常见属性 2.1 前台线程与后台线程 2.2 setDaemon() 2.3 isAlive() Thread类的属性 Thread 类是JVM 用来管理线程的一个类&#xff0c;换句话说&#xff0c;每个线程都有一个唯一的Thread 对象与之关联&am…

论文阅读笔记:DRCT: Saving Image Super-Resolution away from Information Bottleneck

论文阅读笔记&#xff1a;DRCT: Saving Image Super-Resolution away from Information Bottleneck 1 背景1.1 问题1.2 本文提出的方法 2 创新点3 方法4 模块4.1 问题描述4.2 深度特征提取模块4.3 同任务渐进式训练策略 5 效果5.1 和SOTA方法对比 论文&#xff1a;https://arxi…

数据结构 —— 红黑树

目录 1. 初识红黑树 1.1 红黑树的概念 1.2 红⿊树的规则 1.3 红黑树如何确保最长路径不超过最短路径的2倍 1.4 红黑树的效率:O(logN) 2. 红黑树的实现 2.1 红黑树的基础结构框架 2.2 红黑树的插⼊ 2.2.1 情况1&#xff1a;变色 2.2.2 情况2&#xff1a;单旋变色 2.2…

健身房数字化转型:SpringBoot管理系统

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

字符串相乘(全网最快0ms方法)

一&#xff1a;题目 二&#xff1a;思路 解释&#xff1a;每次相乘的结果不进位直接放进同一个数组里&#xff0c;相同位置则新放进的结果即可&#xff0c;最后得到左图的数组&#xff0c;再对其进行进位&#xff0c;得到正确的结果 Q1&#xff1a;数组的大小取多少&#xff1…

虚拟展厅和实景复刻有什么区别?应用场景和优势有哪些?

虚拟展厅和实景复刻在展览展示领域均扮演着重要角色&#xff0c;但二者之间存在显著的差异。以下是对这两者的详细比较&#xff1a; 一、定义与构建方式 虚拟展厅 虚拟展厅是利用数字技术和三维建模技术创建的虚拟展览环境&#xff0c;使参观者可以通过计算机、智能手机等设…

如何删除react项目的默认图标,使在浏览器中不显示默认图标favicon.ico

要删除 React 项目的默认图标&#xff0c;使在浏览器中不显示默认图标favicon.ico&#xff0c;其实有两种方法&#xff1a; 方法一 方法要点&#xff1a;删除掉 public 目录下的 favicon.ico 文件&#xff0c;再用浏览器访问时&#xff0c;如果加载不到图标文件&#xff0c;就…

计算机网络——路由器构成

算路由表是分布式去算——你算你的&#xff0c;我算我的 输出队列非先来先传 调度发生在哪里 缓存队列一般是应对——来数据方向的速度过快问题

项目活动进度计算题

六个时间参数①最早开始时间ESmax{紧前工作最早完成时间EF}&#xff08;紧前取大&#xff09; 最早完成时间EFES工期&#xff0c;从左→右计算&#xff0c;累加取大 ②最迟完成时间LFmin{紧后工作最迟开始时间LS}&#xff08;紧后取小&#xff09; 最迟开始时间LSLF-工期&am…

练习LabVIEW第四十题

学习目标&#xff1a; 用labvIEW做一个循环闪烁指示灯&#xff0c;要能够在前面板调节周期和占空比。 开始编写&#xff1a; 前面板 一个布尔指示灯一维数组&#xff0c;两个数值输入控件&#xff1b; 程序框图 添加一个while循环&#xff0c;循环内添加初始化数组&…