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;}}
相关文章:
java多线程编程(二)一一>线程安全问题, 单例模式, 解决程线程安全问题的措施
引言: 如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的 线程安全问题的原因: 一.操作系统的随机调度 : 二.多个线程修改同一个变量: 三.修改操作不是…...
Leetcode 213. 打家劫舍 II 动态规划
原题链接:Leetcode 213. 打家劫舍 II class Solution { public:int rob(vector<int>& nums) {int n nums.size();if (n 1)return nums[0];if (n 2)return max(nums[0], nums[1]);// 如果偷了第一家,就不能偷最后一家int dp[n - 1];dp[0] …...
就业市场变革:AI时代,我们将如何评估人才?
内容概要 在这个充满变革的时代,就业市场正被人工智能(AI)技术深刻改变。随着技术的进步,传统的人才评估方式逐渐显示出其局限性。例如,过去依赖于纸质简历和面试评估的方式在快速变化的环境中难以准确识别真实的人才…...
富格林:安全操作方式稳健出金
富格林认为,黄金一直是吸引投资者关注的投资产品之一,投资者不断踏入黄金投资交易市场。很多投资者都以为现货黄金投资是很容易实现出金获得丰厚利润,但是面对复杂的交易市场,不仅不能轻易实现安全获利出金,甚至可能还…...
早点包子店点餐的软件下载和点餐操作教程 佳易王餐饮点餐管理系统操作方法
一、概述 【软件试用版资源文件可以点文章最后卡片了解】 早点包子店点餐的软件下载和点餐操作教程 适合于早点早餐餐饮行业的软件,实现早点点餐,收银会员管理,库存统计,销售统计等一体化操作。 点餐的时候可以用手触摸点&…...
uniapp一键打包
1.先安装python环境, 2.复制这几个文件到uniapp项目里面 3.修改自己证书路径,配置文件路径什么的 4.在文件夹页面双击buildController.py或者cmd直接输入buildController.py 5.python报错,哪个依赖缺少安装哪个依赖 6.执行不动的话&…...
什么是ksqlDB?流处理世界里的新范式
在大数据技术快速迭代的今天,我们见证了数据处理范式的不断演进。从批处理到流处理,从复杂的编程框架到声明式API,技术在不断简化与进化。而ksqlDB的出现,为我们带来了一个全新的视角 - 它不仅仅是一个流处理引擎,更是重新定义了我们与实时数据交互的方式。 让我们重新认识流处…...
Vue.js组件开发
Vue.js 是一个流行的 JavaScript 框架,用于构建用户界面和单页应用程序。开发 Vue.js 组件是 Vue.js 开发的核心部分。下面是一些关于 Vue.js 组件开发的基本概念和示例。 1. 创建一个基本的 Vue 组件 <template><div><h1>{{ title }}</h1>…...
Oracle视频基础1.1.2练习
1.1.2 需求: 查询oracle组件和粒度大小, select component,granule_size from v$sga_dynamic_components;Oracle SGA 中组件和粒度大小查询详解 在 Oracle 数据库的内存结构中,SGA(System Global Area,系统全局区&am…...
Hadoop分布式文件系统架构和设计
Hadoop分布式文件系统架构和设计 引言Hadoop 分布式文件系统 (HDFS) 是一个设计用于在普通硬件上运行的分布式文件系统。它与现有的分布式文件系统有许多相似之处。然而,HDFS 与其他分布式文件系统的差异是显著的。HDFS具有高度的容错能力,并且设计用于在低成本硬件上部署。H…...
Prompt Engineering (Prompt工程)
2 prompt工程2大原则 2.1 给出清晰,详细的指令 策略1:使用分割符清晰的指示输出的不同部分,比如"",<>,<\tag>等分隔符 策略2:指定一个结构化的输出,比如json,html等格式 策略3:要…...
第十四课 Vue中的HTML及文本渲染
Vue中的HTML及文本渲染 HTML渲染 v-html指令可以在DOM中渲染新的子HTML DOM,Vue官方认为HTML渲染是不安全的,并不建议直接做HTML插入操作。 <div id"app"><div v-html"vals"></div></div><script>n…...
无人机救援系统简单解读
无人机救援系统简单解读 1. 源由2. 场景分析2.1 人员搜索2.2 紧急物资投送2.3 环境评估 3. 系统分解4. 初步总结5. 参考资料 1. 源由 最近,关于《Rapid Response UAV Post-Disaster Location Network Incorporating ML, Radio Control, and Global Positioning Sys…...
广西自闭症儿童寄宿学校:打造温馨成长的家
在广西这片美丽的土地上,有一群特殊的孩子,他们生活在自己的世界里,对外界的喧嚣似乎无动于衷,他们就是自闭症儿童。自闭症,这个看似遥远的词汇,却实实在在影响着许多家庭。幸运的是,在这片热土…...
python 查看服务器主机 IP 地址
import socket hostname socket.gethostname() ## 获取主机名 ip_address socket.gethostbyname(hostname) # 通过主机名获取 IP 地址 print(“服务器主机 IP 地址为:”, ip_address)...
应对市场变化与竞争对手挑战的策略
应对市场和竞争对手的变化需要企业具备敏锐的市场洞察力、灵活的战略调整能力、持续的创新意识、有效的资源配置等关键能力。敏锐的市场洞察力是企业能够及时捕捉市场趋势和竞争动态的基础,它不仅帮助企业预见潜在的机会和威胁,还能指导企业制定更具前瞻…...
CSS_定位_网页布局总结_元素的显示与隐藏
目录 目标 1. 定位 1.1 为什么需要定位 1.2 定位组成 1. 定位模式 2. 边偏移 1.3 静态定位 static(了解) 1.4 相对定位 relative(重要) 1.5 绝对定位 absolute(重要) 1.6 子绝父相的由来ÿ…...
内存映射区
存储映射区介绍 存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。从缓冲区中取数据,就相当于读文件中的相应字节;将数据写入缓冲区,则会将数据写入文件。这样,就可在不使用read和write函数的情况…...
es安装拼音分词后Kibana出现内存错误
出现错误 今天在安装es的拼音分词器,并重启es容器后,登录Kibana无法使用,查询日志发现如下报错 Waiting until all Elasticsearch nodes are compatible with Kibana before starting saved objects migrations... | typelog timestamp2024…...
mysql 字符串拼接文本并换行
描述: 拼接字符串文本,文本需要换行 函数: concate(‘A串’,char(10),‘B串’),其中char(10)代表换行 案例: select concat(问题一:组织错误,char(10),问题二࿱…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...



