Java并发学习之Volatile及内存模型探究
volatile工作原理
java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。 Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。
若想清楚理解volatile
关键字是如何保障共享变量在多线程之间正常使用的需要了解以下几点
- java的内存模型
- 原子性,可见性,有序性
- volatile的工作原理
- 测试case
I. Java的内存模型
1. 内存模型
精简一点,概念如下:
指令在CPU中执行,CPU运行速度较快,因此为减少从内存中频繁读写数据的开销,在cpu与内存的操作之间,有个高速缓存的的区域
获取数据流程:
- 从缓存中获取Data
- 缓存中存在,则直接返回
- 缓存中不存在
- 从内存中获取Data数据
- 将Data数据写入缓存
- 返回Data数据
上面的流程中,第一步会导致一致性问题,分析如下
若内存中Data已更新,但缓存中数据未更新,此时返回缓存中Data,返回的是旧数据
解决方案:
- 总线上加LOCK#锁
- 因为CPU和其他部件进行通信都是通过总线来进行的,如果对总线加LOCK#锁的话,也就是说阻塞了其他CPU对其他部件访问(如内存),从而使得只能有一个CPU能使用这个变量的内存
- 缓存一致性协议
- 在内存中数据发生变更后,同时使所有缓存中的数据失效,在访问缓存中数据时,优先判断是否已经失效,若失效则从内存中获取最新的数据回写到缓存中,并返回
2. Java内存模型
java内存模型,主要是为了屏蔽不同的硬件,操作系统的内存访问差异,使Java程序可以达到跨平台的目的,从而定义的一套模型
线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存保存该线程读写共享变量的副本
因此也存在上面的一致性问题,即如何保证线程对共享变量的修改后,其他的线程能访问到最新的共享变量
指令重排序
Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性
举例说明
int i;
boolean ans;
i = 10;
ans = true;
上面的代码中,i
和ans
的赋值先后顺序由于指令重排,可能会出现ans=true
时,i
依然为0的情况
II. 原子性,可见性,顺序性
原子性
表示不可再继续分割
- java中除了 (long,double)的赋值是非原子性的,其他的基本变量、对象的赋值都是原子性的
- ++/-- 操作是非原子性的
可见性
一个线程对共享变量的修改,确保对其他线程可见(即另一个线程能访问到修改后的数据)
- 用
volatile
进行声明变量,保证可见
顺序行
程序执行的顺序按照代码的先后顺序执行
-
volatile
禁止指令重排 - 在修改变量时,加锁(synchronized,lock),确保同一时刻只有一个线程修改变量
III. Volatile关键字
用法
- 在变量前面加上
volatile
即可
作用
- 确保共享变量的修改,对其他线程都是立即可见的
- 禁止指令重排(即当访问or修改
volatile
修饰的共享变量时,确保前面的代码都执行完了)
原理和实现机制
- 修改
volatile
声明的共享变量,会强制要求修改后的值写入内存,并失效其他线程的本地内存中的副本 - 汇编之后,加入volatile关键字时,会多出一个lock前缀指令
- 它确保指令重排序时不会把其后面的指令排到lock指令之前,也不会把前面的指令排到lock指令之后
IV. 使用场景&小结
1.volatile
关键字无法保证操作的原子性
2.volatile
关键字,禁止共享变量的指令重排,确保修改对所有线程立即可见
3.使用场景
- 对变量的写操作不依赖于当前值
- (因为volatile不保证原子性,若依赖自己的值)
- 变量独立使用
- 如 volatile 定义变量i,还有一个没有被volatile修饰的变量j
-
int ans = i + j;
(也无法保障准确性)
经典的单例case写法
class Singleton{
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
参考
- Java文件上传与下载【面试+工作】
- QBC查询
- 一条delete语句的调优(r4笔记第86天)
- Java支付宝接口开发【面试+工作】
- 03.SVN检出/解决冲突/提交
- Spring思维导图,让Spring不再难懂(mvc篇)
- SQL优化一(SQL使用技巧)
- Spring思维导图,让Spring不再难懂(aop篇)
- MongoDB初探第二篇 (r4笔记第82天)
- Spring思维导图,让Spring不再难懂(cache篇)
- 曲折的10g,11g中EM的安装配置过程(r4笔记第98天)
- Linux 学习记录 一(安装、基本文件操作).
- 实用的位运算应用(r4笔记第97天)
- 关于date格式的两个案例(r4笔记第96天)
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 蜂鸟E203系列——定时器中断设计
- FPGA信号截位策略
- 数字IC设计经典笔试题之【verilog篇】
- 蜂鸟E203系列——Windows下运行hello world例程
- 蜂鸟E203系列——Windows开发工具
- FPGA奇数分频
- 形式化分析工具:在虚拟操作系统和主机操作系统之间配置共享文件夹
- 「PHP」以nginx、php-cgi为例,把nginx、php-cgi安装为Windows系统服务
- 气象编程 | Bash拍了拍你说:掌握了我,工作效率起码提高5倍
- 优雅的使用Go进行单元测试
- 雷达LFM信号分析
- 单脉冲测角处理
- 脉冲压缩处理
- 气象编程 | 科学计算库Scipy简易入门
- 两态数据类型