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

学习日志:volatile 关键字

文章目录

  • 前言
  • 一、volatile 关键字
  • 二、禁止指令重排序
  • 二、双重检验锁方式实现单例模式
  • 三、volatile 不能保证原子性


前言

在 Java 中,volatile 关键字可以保证变量的可见性,如果将变量声明为 volatile ,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。


一、volatile 关键字

volatile 关键字其实并非是 Java 语言特有的,在 C 语言里也有,它最原始的意义就是禁用 CPU 缓存。如果我们将一个变量使用 volatile 修饰,这就指示 编译器,这个变量是共享且不稳定的,每次使用它都到主存中进行读取
在这里插入图片描述
在这里插入图片描述
JMM(Java 内存模型)强制在主存中进行读取

volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。

二、禁止指令重排序

在 Java 中,volatile 关键字除了可以保证变量的可见性,还有一个重要的作用就是防止 JVM 的指令重排序。 如果我们将变量声明为 volatile ,在对这个变量进行读写操作的时候,会通过插入特定的 内存屏障 的方式来禁止指令重排序。

在 Java 中,Unsafe 类提供了三个开箱即用的内存屏障相关的方法,屏蔽了操作系统底层的差异:

public native void loadFence();
public native void storeFence();
public native void fullFence();

理论上来说,这个三个方法也可以实现和volatile禁止重排序一样的效果,只是会麻烦一些。

二、双重检验锁方式实现单例模式

双重校验锁实现对象单例(线程安全)

public class Singleton {private volatile static Singleton uniqueInstance;private Singleton() {}public  static Singleton getUniqueInstance() {//先判断对象是否已经实例过,没有实例化过才进入加锁代码if (uniqueInstance == null) {//类对象加锁synchronized (Singleton.class) {if (uniqueInstance == null) {uniqueInstance = new Singleton();}}}return uniqueInstance;}
}

uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:

  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。

例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。

三、volatile 不能保证原子性

volatile 关键字能保证变量的可见性,但不能保证对变量的操作是原子性的。
例如:

public class VolatileAtomicityDemo {public volatile static int inc = 0;//对volatile变量inc执行+1操作public void increase() {inc++;}public static void main(String[] args) throws InterruptedException {//创建线程池ExecutorService threadPool = Executors.newFixedThreadPool(5);VolatileAtomicityDemo volatileAtomicityDemo = new VolatileAtomicityDemo();//使用每个线程调用increase方法去执行500次+1操作for (int i = 0; i < 5; i++) {threadPool.execute(() -> {for (int j = 0; j < 500; j++) {volatileAtomicityDemo.increase();}});}// 等待1.5秒,保证上面程序执行完成Thread.sleep(1500);System.out.println(inc);threadPool.shutdown();}
}

正常情况下,运行上面的代码理应输出 2500。但真正运行了上面的代码之后,会发现每次输出结果都小于 2500。

也就是说,如果 volatile 能保证 inc++ 操作的原子性的话。每个线程中对 inc 变量自增完之后,其他线程可以立即看到修改后的值。5 个线程分别进行了 500 次操作,那么最终 inc 的值应该是 5*500=2500。

很多人会误认为自增操作 inc++ 是原子性的,实际上,inc++ 其实是一个复合操作,包括三步:

  1. 读取 inc 的值。
  2. 对 inc 加 1。
  3. 将 inc 的值写回内存。

volatile 是无法保证这三个操作是具有原子性的,有可能导致下面这种情况出现:

  1. 线程 1 对 inc 进行读取操作之后,还未对其进行修改。线程 2 又读取了 inc的值并对其进行修改(+1),再将inc的值写回内存。
  2. 线程 2 操作完毕后,线程 1 对 inc的值进行修改(+1),再将inc 的值写回内存。

这也就导致两个线程分别对 inc 进行了一次自增操作后,inc 实际上只增加了 1。

其实,如果想要保证上面的代码运行正确也非常简单,

利用 synchronized、Lock或者AtomicInteger都可以。

public synchronized void increase() {inc++;
}
public AtomicInteger inc = new AtomicInteger();public void increase() {inc.getAndIncrement();
}
Lock lock = new ReentrantLock();
public void increase() {lock.lock();try {inc++;} finally {lock.unlock();}
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Java | 自制AWT单词猜一猜小游戏(测试版)
  • 通过vue3 + TypeScript + uniapp + uni-ui 实现下拉刷新和加载更多的功能
  • 宝塔Wordpress 插件 Redis object cache 导致内存很高 80%以上的原因和解决
  • 基于最新版的flutter pointycastle: ^3.9.1的AES加密
  • 一、C#概述
  • cordova使用vue进行开发
  • 题解:T480718 eating
  • 【思科】链路聚合实验配置和背景
  • 自动化产线 搭配数据采集监控平台 创新与突破
  • mysql 安装配置 next 按钮为什么置灰点击不了
  • 3D 渲染一个房屋需要多长时间?
  • chatglm2-6b-prompt尝试
  • SwiftUI 6.0(Xcode 16)新 PreviewModifier 协议让预览调试如虎添翼
  • 路网双线合并单线——ArcGIS 解决方法
  • 全国区块链职业技能大赛国赛考题区块链产品需求分析与方案设计
  • 【RocksDB】TransactionDB源码分析
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • css选择器
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • ECMAScript入门(七)--Module语法
  • javascript 哈希表
  • javascript面向对象之创建对象
  • Java编程基础24——递归练习
  • k8s 面向应用开发者的基础命令
  • Mysql数据库的条件查询语句
  • Netty 框架总结「ChannelHandler 及 EventLoop」
  • supervisor 永不挂掉的进程 安装以及使用
  • vue2.0项目引入element-ui
  • vue和cordova项目整合打包,并实现vue调用android的相机的demo
  • 聊聊flink的BlobWriter
  • 浅谈Golang中select的用法
  • 手写一个CommonJS打包工具(一)
  • 数据仓库的几种建模方法
  • 数据结构java版之冒泡排序及优化
  • 微信公众号开发小记——5.python微信红包
  • 为视图添加丝滑的水波纹
  • 写给高年级小学生看的《Bash 指南》
  • 用quicker-worker.js轻松跑一个大数据遍历
  • UI设计初学者应该如何入门?
  • ​七周四次课(5月9日)iptables filter表案例、iptables nat表应用
  • ​用户画像从0到100的构建思路
  • $redis-setphp_redis Set命令,php操作Redis Set函数介绍
  • (2024,Flag-DiT,文本引导的多模态生成,SR,统一的标记化,RoPE、RMSNorm 和流匹配)Lumina-T2X
  • (30)数组元素和与数字和的绝对差
  • (el-Date-Picker)操作(不使用 ts):Element-plus 中 DatePicker 组件的使用及输出想要日期格式需求的解决过程
  • (pojstep1.3.1)1017(构造法模拟)
  • (附源码)springboot家庭财务分析系统 毕业设计641323
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (黑客游戏)HackTheGame1.21 过关攻略
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (一)UDP基本编程步骤
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • ***测试-HTTP方法
  • .env.development、.env.production、.env.staging