Java Volatile的三大特性
本文通过学习:周阳老师-尚硅谷Java大厂面试题第二季 总结的volatile相关的笔记
volatile是Java虚拟机提供的轻量级的同步机制,三大特性为:
保证可见性、不保证原子性、禁止指令重排
一、保证可见性

import java.util.concurrent.TimeUnit;class MyData {//主物理内存volatile int number = 0;public void addTo60() {this.number = 60;}
}public class VolatileDemo {public static void main(String args []) {MyData myData = new MyData();new Thread(() -> {System.out.println(Thread.currentThread().getName() + "\t come in");try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}myData.addTo60();System.out.println(Thread.currentThread().getName() + "\t update number value:" + myData.number);}, "AAA").start();while(myData.number == 0) {}//说明AAA线程在睡眠3秒后,更新的number的值,重新写入到主内存,并被main线程感知到了System.out.println(Thread.currentThread().getName() + "\t mission is over");}
}
/*** AAA come in* AAA update number value:60* main mission is over //若number=0没被volatile修饰,则这句不打印*/
二、不保证原子性
1、代码示例

import java.util.concurrent.TimeUnit;class MyData {volatile int number = 0;public void addPlusPlus() {number ++;}
}public class VolatileDemo {public static void main(String args []) {MyData myData = new MyData();for (int i = 0; i < 20; i++) {new Thread(() -> {for (int j = 0; j < 1000; j++) {myData.addPlusPlus();}}, String.valueOf(i)).start();}// 需要等待上面20个线程都计算完成后,在用main线程取得最终的结果值// 这里判断线程数是否大于2,为什么是2?因为默认是有两个线程的,一个main线程,一个gc线程while(Thread.activeCount() > 2) {Thread.yield();//yield表示不执行}// 最终输出的值应该=20*1000=20000System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);//19504}
}
2、数值丢失的原因?
线程1和2同时修改各自工作空间中的内容,因为可见性,需要重写入内存,但
线程1在写入的时候,线程2也同时写入,导致线程1的写入操作被挂起,导致
线程2先写,线程1后写,线程1的值覆盖了线程2的值,因此数据丢失。
n++这条命令,被拆分成了3个指令:
-getfield 从主内存拿到原始n
-iadd 进行加1操作
-putfileld 把累加后的值写回主内存
假如三个线程同时通过getfield命令,拿到主存中的n值,然后三个线程,各自在自己的工作内存中进行加1操作,但他们并发进行 iadd 命令的时候,因为只能一个进行写,所以其它操作会被挂起,假设1线程,先进行了写操作,在写完后,volatile的可见性,应该需要告诉其它两个线程,主内存的值已经被修改了,但是因为太快了,其它两个线程,陆续执行 iadd命令,进行写入操作,这就造成了其他线程没有接受到主内存n的改变,从而覆盖了原来的值,出现写丢失,这样也就让最终的结果少于200
3、解决办法(synchronized / AtomicInteger)
public synchronized void addPlusPlus() { number ++; } |
AtomicInteger atomicInteger = new AtomicInteger(); public void addAtomic() { atomicInteger.getAndIncrement(); } |
三、禁止指令重排
指令重排的代码示例
public class ResortSeqDemo { int a= 0; boolean flag = false; public void method01() { a = 1; flag = true; } public void method02() { if(flag) { a = a + 5; System.out.println("reValue:" + a); } } } | 【顺序执行】 a=1 flag=true a=a+5 顺序执行,打印reValue:6 【指令重排】 flag=true a=a+5 打印reValue:5 a=1 |
四、应用-单例模式
![]() | 方法1. synchronized ![]() 方法2. 禁用指令重排 + DCL双端检锁 ![]() DCL = Double Check Lock 双端检锁机制 ![]() |
public class SingletonDemo {private static volatile SingletonDemo instance = null;private SingletonDemo() {System.out.println(Thread.currentThread().getName() + "\t 我是构造方法SingletonDemo");}public static SingletonDemo getInstance() {if(instance == null) {synchronized (SingletonDemo.class) {if(instance == null) {instance = new SingletonDemo();}}}return instance;}public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(() -> {SingletonDemo.getInstance();}, String.valueOf(i)).start();}}
}
/*
* 0 我是构造方法SingletonDemo
*/
原因是在某一个线程执行到第一次检测的时候,读取到 instance 不为null,instance的引用对象可能没有完成实例化。因为 instance = new SingletonDemo();可以分为以下三步进行完成:
memory = allocate(); // 1、分配对象内存空间
instance(memory); // 2、初始化对象
instance = memory; // 3、设置instance指向刚刚分配的内存地址,此时instance != null
但是我们通过上面的三个步骤,能够发现,步骤2 和 步骤3之间不存在 数据依赖关系,而且无论重排前 还是重排后,程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。
memory = allocate(); // 1、分配对象内存空间
instance = memory; // 3、设置instance指向刚刚分配的内存地址,此时instance != null,但是对象还没有初始化完成
instance(memory); // 2、初始化对象
这样就会造成什么问题呢?
也就是当我们执行到重排后的步骤2,试图获取instance的时候,会得到null,因为对象的初始化还没有完成,而是在重排后的步骤3才完成,因此执行单例模式的代码时候,就会重新在创建一个instance实例
指令重排只会保证串行语义的执行一致性(单线程),但并不会关系多线程间的语义一致性
所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,这就造成了线程安全的问题, 因此需要引入volatile,来保证出现指令重排的问题,从而保证单例模式的线程安全性。
相关文章:

Java Volatile的三大特性
本文通过学习:周阳老师-尚硅谷Java大厂面试题第二季 总结的volatile相关的笔记volatile是Java虚拟机提供的轻量级的同步机制,三大特性为:保证可见性、不保证原子性、禁止指令重排一、保证可见性import java.util.concurrent.TimeUnit;class M…...

Android Compose——一个简单的Bilibili APP
Bilibili移动端APP简介依赖效果登录效果WebView自定义TobRow的Indicator大小首页推荐LazyGridView使用Paging3热门排行榜搜索模糊搜索富文本搜索结果视频详情合集信息Coroutines进行网络请求管理,避免回调地狱添加suspendwithContextGit项目链接末简介 此Demo采用A…...

二叉树的最近公共祖先【Java实现】
题目描述 现有一棵n个结点的二叉树(结点编号为从0到n-1,根结点为0号结点),求两个指定编号结点的最近公共祖先。 注:二叉树上两个结点A、B的最近公共祖先是指:二叉树上存在的一个结点P,使得P既是…...

关闭应用程序遥测,禁止Windows收集用户信息
目录 1. 先创建还原点,防止意外 2. 界面设置 3. 服务 (1) GPEdit.msc - 本地计算机策略 - 计算机配置 - 管理模板 - Windows 组件 - 应用程序兼容性 - 关闭应用程序遥测 - 已启用 (2) GPEdit.msc - 本地计算机策略 - 计算机配置 - 管理模板 - Windows 组件 - 数…...

【备战面试】每日10道面试题打卡-Day4
本篇总结的是Java集合知识相关的面试题,后续也会更新其他相关内容 文章目录1、HashMap在JDK1.7和JDK1.8中有哪些不同?2、HashMap 的长度为什么是2的幂次方?3、HashMap的扩容操作是怎么实现的?4、HashMap是怎么解决哈希冲突的&…...

热乎的面经——初出茅庐
⭐️前言⭐️ 本篇文章记录博主与2023.03.04面试上海柯布西公司,一面所被问及的面试问题,回答答案仅供参考。 🍉欢迎点赞 👍 收藏 ⭐留言评论 📝私信必回哟😁 🍉博主将持续更新学习记录收获&am…...
数据库中各种锁汇总
本文汇总简记数据库中的各种锁。 名称英文名称定义解释悲观锁Pessimistic Lock在访问数据前先加锁,防止其他事务的并发修改数据通过获取锁来保证数据的独占性,从而避免并发修改数据带来的问题。乐观锁Optimistic Lock在修改数据时先不加锁,而…...

p76 - Python 开发-内外网收集 Socket子域名DNS
数据来源 Python 开发相关知识点: 1.开发基础环境配置说明 Windows10Pycharm 2.Python 开发学习的意义 学习相关安全工具原理 掌握自定义工具及拓展开发解决实战中无工具或手工麻烦批量化等情况 在二次开发 Bypass,日常任务,批量测试利用…...
QCC51XX--eFush Key加密
https://blog.csdn.net/weixin_42162924/article/details/125828901?spm=1001.2014.3001.5502 在开始讲eFush Key加密操作之前,说一下这个操作的作用就是将自己的固件采用硬件的方式进行加密。 操作步骤 1.创建一个txt文本文件,参考文档“Qualcomm BlueSuite v3.1.4 Release…...

nginx http模块
1.模块依赖2. 模块的初始化2.1 location的定义location的定义包含以下几种location [ | ~ | ~* | ^~ ] uri { ... } location name { ... }:表示精确匹配,只有请求的url路径与后面的字符串完全相等时,才会命中,不支持location嵌套~ÿ…...

守护进程 || 精灵进程
目录 守护进程(deamon) || 精灵进程 特点 什么是前台进程组 把自己写的服务器deamon deamon代码 守护进程(deamon) || 精灵进程 特点 01. 他的PPID是1(附件特征)02. COMMAND --- 称为进程启动的命令03…...

Zookeeper3.5.7版本——客户端命令行操作(znode 节点数据信息)
目录一、命令行语法二、znode 节点数据信息2.1、查看当前znode中所包含的内容2.2、查看当前节点详细数据2.3、节点详细数据解释一、命令行语法 命令行语法列表 命令基本语法功能描述help显示所有操作命令ls path使用 ls 命令来查看当前 znode 的子节点 [可监听]-w 监听子节点变…...

如何写好单测
1、为什么要写单测? 单测即单元测试(Unit Test),是对软件的基本组成单元进行的测试,比如函数、过程或者类的方法。其意义是: 功能自测,发现功能缺陷自我Code Review测试驱动开发促进代码重构并…...
CDH-6.3.2内置spark-2.4.0的BUG
1. 背景 公司最近在新建集群,全部采用开源的大数据框架,并且将之前使用的阿里云的所有服务进行下线,其中就涉及到了旧任务的迁移。 2. 任务 2.1. 简述 我接手到一个之前的 spark 任务,是读取阿里 LogStore 数据,然…...

SpringCloud之ElasticSearch笔记
ElasticSearch 初识ElasticSearch ElasticSearch是什么 ElasticSearch一个基于Lucene的底层的开源的分布式搜索引擎,可用来实现搜索,日志统计,分析,系统监控 正向索引和倒排索引 正向索引:逐条扫描(my…...

数字图像学笔记 —— 17. 图像退化与复原(自适应滤波之「最小二乘方滤波」)
文章目录维纳滤波的缺点约束最小二乘方滤波给一个实际例子吧维纳滤波的缺点 维纳滤波(Wiener Filter),虽然是一种非常强大的退化图像还原算法,但是从实验过程我们也发现它存在着致命的缺陷,那就是要求输入退化系统的 …...

2023-03-05:ffmpeg推送本地视频至lal流媒体服务器(以RTMP为例),请用go语言编写。
2023-03-05:ffmpeg推送本地视频至lal流媒体服务器(以RTMP为例),请用go语言编写。 答案2023-03-05: 使用 github.com/moonfdd/ffmpeg-go 库。 先启动lal流媒体服务器软件,然后再执行命令: go…...

MathType7最新版免费数学公式编辑器
话说我也算是 MathType准资深(DB)用户了,当然自从感觉用DB不好之后,我基本上已经抛弃它了,只是前不久因为个别原因又捡起来用了用,30天试用期间又比较深入的折腾了下,也算是变成半个MathType砖家,coco玛奇朵简单介绍一下这款软件:在很可能看到这儿的你还没有出生的某个年月&…...

一文带你入门angular(中)
一、angular中的dom操作原生和ViewChild两种方式以及css3动画 1.原生操作 import { Component } from angular/core;Component({selector: app-footer,templateUrl: ./footer.component.html,styleUrls: [./footer.component.scss] }) export class FooterComponent {flag: b…...
单例设计模式共享数据问题分析、解决(c++11)设计多线程。
系列文章目录 单例设计模式共享数据问题分析、解决; 文章目录系列文章目录前言一、单例模式1.1 基本概念1.2 单例设计模式共享数据问题分析、解决1.3 std::call_once()介绍二、代码案例1.代码示例总结前言 关键内容:c11、多线程、共享数据、单例类 本章内容参考git…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...

UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
Python竞赛环境搭建全攻略
Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型(算法、数据分析、机器学习等)不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...