【技术积累】Java里的volatile关键字到底能干嘛?
7.4 最害怕的一集 - volatile
7.4.1 最简单的一集 - volatile 语义 (难度 : )
读 -> 读一个 volatile 必须从 主内存读
写 -> 写一个 volatile 会把 本地内存 写到 主内存去
7.4.2 最好理解的一集 - volatile 保证了 可见性 ( 难度 : )
public class VolatileSTest {
public static void main(String[] args) throws InterruptedException {
Data data = new Data();
new Thread(() -> {
while (true) {
if (data.bool) {
System.out.println("溜了溜了,线程结束了喵!");
break;
}
}
}).start();
TimeUnit.SECONDS.sleep(1);
// 保证新线程后修改
new Thread(() -> {
data.bool = true;
}).start();
}
}
class Data {
// boolean bool = false;
// volatile boolean bool = false;
}
理解方法:
如果去掉 volatile : 就会发现 死循环了
如果不去掉 volatile : 就会发现 没死循环
自然就很好理解他的 可见性保证
7.4.3 最难复现的一集 - volatile 的 有序性保证 ( 难度 : )
为什么给四颗, 因为首先,凭空想想不到,而且真要复现出重排序很难, 个人运行了几千次线程都出不来
class Number {
int i = 1;
volatile int v = 1;
public void set() {
i = 2;
v = 2;
}
public void show() {
if (v == 2) {
System.out.println("t = " + i);
}
}
}
重排序
问题一
两个线程 :
一个调用 show , 一个线程调用 set
重复一遍 !!!! 两个线程: 一个调用 show , 一个线程调用 set
重复一遍 !!!! 两个线程: 一个调用 show , 一个线程调用 set
重复一遍 !!!! 两个线程: 一个调用 show , 一个线程调用 set
因为在单线程中 , 先调用set方法 ,再调用show方法, 因为 happens-before , 是不会允许重排序的
线程一: {
i = 2;
v = 2;
}
线程二: {
if (v == 2) {
System.out.println("t = " + i);
}
}
在不使用 volatile 的情况下
明显,在单线程中 ,两个线程执行的方法体是没有相互依赖的
所以是可以重排序的,所以可以出现
线程一: {
v = 2
i = 2
}
线程二: {
// 先读取 i (即 先准备一下sout)
if(v == 2){
把准备好的i输出
}
}
明显会出现
错误情况一: v = 2 , i = 1 导致 线程二输出 i = 1
错误情况二: v = 2 , i = 2 ,但是!!因为线程二可以准备好 i ,所以线程二输出 i = 1
都是错的
而使用 volatile 就可以避免这些情况
Q: 欸欸欸!凭什么show一定在set之后啊?就不能先show再set吗?
A: 干嘛?先show的话, 线程二没有输出呗,然后呢???有什么问题吗???
问题二
public class Singleton {
private volatile static Singleton instance = null;
public static Singleton getInstance() {
if(null == instance) {
synchronized (Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
这个就不说了,
instancec 实例化过程:
分配内存空间
初始化对象
将对象指向刚分配的内存空间
变成
分配内存空间
将对象指向刚分配的内存空间
初始化对象
然后初始化之前又被别的线程获取到了 instance 异常, 就会出现null
7.4.4 最讨厌的一集 - 啥限制重排序啊 ( 难度: )
还是先记一下结论再去理解叭 : 读后写前
读后写前 : volatile读后禁止一且读写重排序, volatile写前禁止一切重排序
理解:
首先要记住,都禁止重排序了, 怎么可能跟可见性有关呢 ?
一定是和 有序性相关。
class Number {
int i = 1;
volatile int v = 1;
public void set() {
i = 2;
v = 2; // 写 voaltile值
}
public void show() {
if (v == 2) { // 读 volatile值
System.out.println("t = " + i);
}
}
}
首先,记住7.4.1:
读 -> 读一个 volatile 必须从 主内存读
写 -> 写一个 volatile 会把 本地内存 写到 主内存去
然后:
先看看特例分析
看show方法: 如果重排序, 是不是会出现 7.4.3 中的 情况二的问题
下面是定性分析
\首先,volatile读写之间肯定要禁止重排序叭,重点不好理解的肯定是为什么 v读之后不可以普通读
对比一下:( 如果真的出现所谓 v读和后面的操作出现重排序会发生什么 )
① v读 -> 读
② v读 -> 写
③ 读 -> v读
④ 写 -> v读
① ③ 对比:
① : 读主内存,然后放到工作内存 -> 读工作内存
③ : 读工作内存 -> 读主内存,放到工作内存
有没有发现,①③普通读的工作内存来源不同?一个是之前的,一个是之后的
② ④ 对比:
② :读主内存,然后放到工作内存 -> 改变工作内存,然后刷盘到主内存
④ :改变工作内存,然后刷盘到主内存 -> 读主内存,然后放到工作内存
有没有发现,②④volatile读到的主内存来源不同?一个是之前的,一个是之后的
所以 读后不可以重排序
写前同理
7.4.5 最抽象的一集 - 内存屏障 ( 难度: )
写的前后有 StoreStore屏障, StoreLoad屏障
读的后面又 LoadStore屏障, LoadLoad屏障
真恶心,啥跟啥啊 ?
记忆规律: 屏障两个单词, 写是不是Store, 所以是 Store + (Store / Load屏障)
读是不是Load, 所以是 Load + (Store / Load屏障)

算了吧, 要这么细是我不配了
配合 7.4.5 去理解噢,内存屏障我就是说得出来,但是就感觉稀里糊涂不知道什么玩意,就是感觉抽象,我是找不到文章能把这玩意讲的清清楚楚,看完就忘了属于是
7.4.6 最好笑的一级 - volatile干嘛的
无论是看关于volatile的视频还是文章,每次看完我都有种 “ 噢 ,牛逼 ,但是原神是由...(恼)” 的感觉,不是吗
其实吧,如果没有涉及到 多线程公共资源的修改,
volatile 就是FW
所以大多数情况,volatile 就是个FW(大概)
但是,涉及到多线程公共资源的修改,就不一样了!!! 见7.4.6
所以它干嘛用的?
两点: ① 保证可见性 ② 保证有序性
啊 ? 那前面什么最讨厌的一集?最难复现的一集干嘛的?
给你看看什么情况下volatile居然不是废物诶!原来读前写后是这样的哇!
7.4.7 最震惊的一集 - 真复现出来了 ( 重排序复现 )
public class OutOfOrderExecution {
private static int i = 0, j = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int count = 0; // 计数
while (true) {
count++;
i = 0;
j = 0;
a = 0;
b = 0;
Thread one = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
i = b;
}
});
Thread two = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
j = a;
}
});
two.start();
one.start();
one.join();
two.join();
String result = "第" + count + "次( i= " + i + ", j= " + j + ")";
if (i == 0 && j == 0) {
System.out.println(result);
break;
} else {
System.out.println(result);
}
}
}
}
最后会出现一次 i = 0 j = 0的情况 ?!
【技术积累】Java里的volatile关键字到底能干嘛?的更多相关文章
- 深入理解Java中的volatile关键字
在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...
- java并发系列(六)-----Java并发:volatile关键字解析
在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保证可见性 ...
- java中的volatile关键字
java中的volatile关键字 一个变量被声明为volatile类型,表示这个变量可能随时被其他线程改变,所以不能把它cache到线程内存(如寄存器)中. 一般情况下volatile不能代替syn ...
- java 轻量级同步volatile关键字简介与可见性有序性与synchronized区别 多线程中篇(十二)
概念 JMM规范解决了线程安全的问题,主要三个方面:原子性.可见性.有序性,借助于synchronized关键字体现,可以有效地保障线程安全(前提是你正确运用) 之前说过,这三个特性并不一定需要全部同 ...
- Java 并发:volatile 关键字解析
摘要: 在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保 ...
- Java中的volatile关键字的功能
Java中的volatile关键字的功能 volatile是java中的一个类型修饰符.它是被设计用来修饰被不同线程访问和修改的变量.如果不加入volatile,基本上会导致这样的结果:要么无法编写多 ...
- java中的Volatile关键字使用
文章目录 什么时候使用volatile Happens-Before java中的Volatile关键字使用 在本文中,我们会介绍java中的一个关键字volatile. volatile的中文意思是 ...
- 一起来看看java并发中volatile关键字的神奇之处
并发编程中的三个概念: 1.原子性 在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行. 2.可见性 对于可见性,Java提供了volati ...
- 并发编程之 Java 内存模型 + volatile 关键字 + Happen-Before 规则
前言 楼主这个标题其实有一种作死的味道,为什么呢,这三个东西其实可以分开为三篇文章来写,但是,楼主认为这三个东西又都是高度相关的,应当在一个知识点中.在一次学习中去理解这些东西.才能更好的理解 Jav ...
- Java中的volatile关键字为什么不是不具有原子性
Java中long赋值不是原子操作,因为先写32位,再写后32位,分两步操作,而AtomicLong赋值是原子操作,为什么?为什么volatile能替代简单的锁,却不能保证原子性?这里面涉及volat ...
随机推荐
- 2021-02-03:手写代码:KMP算法。
福哥答案2021-02-03: Knuth-Morris-Pratt 字符串查找算法,简称为 KMP算法,常用于在一个文本串 S 内查找一个模式串 P 的出现位置.这个算法由 Donald Knuth ...
- 17.AQS中的Condition是什么?
欢迎关注:王有志 期待你加入Java人的提桶跑路群:共同富裕的Java人 今天来和大家聊聊Condition,Condition为AQS"家族"提供了等待与唤醒的能力,使AQS&q ...
- 《数据结构(C语言版)》严蔚敏代码实现———链表
一.前言 哈喽,大家好~我是熊子q,我又来了! 他来了他来了,他带着代码过来了! 今天要分享的代码是链表!快快搬着小板凳! 二.代码 严奶奶的书中预定义了一些预定义常量和类型,大家可以 新建一个y.h ...
- Iframe在Vue中的状态保持技术
引言 Iframe是一个历史悠久的HTML元素,根据MDN WEB DOCS官方介绍,Iframe定义为HTML内联框架元素,表示嵌套的Browsing Context,它能够将另一个HTML页面嵌入 ...
- Linux,会这些就够了
在测试当中,其实对Linux的要求不高,我们在工作中需要记住常用的一些命令,不常用的实际用到的时候再查在记即可,最重要我们要使用命令可以查看日志,定位bug 目录篇: 可用 pwd 命令查看用 ...
- 代码随想录算法训练营Day38 动态规划
代码随想录算法训练营 代码随想录算法训练营Day38 动态规划|理论基础 509. 斐波那契数 70. 爬楼梯 746. 使用最小花费爬楼梯 理论基础 动态规划,英文:Dynamic Programm ...
- FreeSWITCH添加自定义endpoint
操作系统 :CentOS 7.6_x64 FreeSWITCH版本 :1.10.9 日常开发过程中会遇到需要扩展FreeSWITCH对接其它系统的情况,这里记录下编写FreeSWITCH ...
- 在asp.net core web api中添加efcore使用codefirst
首先创建webapi项目,我这里使用的版本是.net6 在nuget中添加对应的工具包 红框标出来的是对应的数据库扩展包,mysql用mysql版,sqlserver用sqlserver版,选择正确的 ...
- 驱动开发:内核封装WFP防火墙入门
WFP框架是微软推出来替代TDIHOOK传输层驱动接口网络通信的方案,其默认被设计为分层结构,该框架分别提供了用户态与内核态相同的AIP函数,在两种模式下均可以开发防火墙产品,以下代码我实现了一个简单 ...
- Go语言中的结构体:灵活性与可扩展性的重要角色
1. 引言 结构体是Go语言中重要且灵活的概念之一.结构体的使用使得我们可以定义自己的数据类型,并将不同类型的字段组合在一起,实现更灵活的数据结构.本文旨在深入介绍Go语言中的结构体,揭示其重要性和灵 ...