线程基础知识10-volatile
1 简介
Volatile保证了可见性和有序性,没有保证原子性。
1.1 保证可见性简介
可见性就是指当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改。volatile变量做到了这一点。
Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此。普通变量与volatile变量的区别是,volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此我们可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点
1.2 保证有序性简介
在对Volatile修饰的变量的写操作前面会加一个StoreStore屏障,后面加一个StoreLoad屏障
在对Volatile修饰的变量的读操作后面会加一个LoadLoad屏障和一个LoadStore屏障
这样子保证了有序性
1.3 volatile的内存语义
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。
当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量
所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取。
在每一个volatile写操作前面插入一个StoreStore屏障
在每一个volatile写操作后面插入一个StoreLoad屏障
在每一个volatile读操作后面插入一个LoadLoad屏障
在每一个volatile读操作后面插入一个LoadStore屏障

2 Java内存模型中的8种工作内存与主内存之间的原子操作
read(读取)→load(加载)→use(使用)→assign(赋值)→store(存储)→write(写入)→lock(锁定)→unlock(解锁)
1)read: 作用于主内存,将变量的值从主内存传输到工作内存,主内存到工作内存
2)load: 作用于工作内存,将从主内存传输的变量值放入工作内存变量副本中,即数据加载
3)use: 作用于工作内存,将工作内存变量副本的值传递给执行引擎,每当JVM遇到需要该变量的字节码指令时会执行该操作
4)assign: 作用于工作内存,将从执行引擎接收到的值赋值给工作内存变量,每当JVM遇到一个给变量赋值字节码指令时会执行该操作
5)store: 作用于工作内存,将赋值完毕的工作变量的值写回给主内存
6)write: 作用于主内存,将store传输过来的变量值赋值给主内存中的变量。
由于上述只能保证单条指令的原子性,针对多条指令的组合性原子保证,没有大面积加锁,所以,JVM提供了另外两个原子指令:
7)lock: 作用于主内存,将一个变量标记为一个线程独占的状态,只是写(write)时候加锁,就只是锁了写变量的过程。
8)unlock: 作用于主内存,把一个处于锁定状态的变量释放,然后才能被其他线程占用
3 JAVA的可见性和有序性问题介绍
https://www.cnblogs.com/jthr/p/15969561.html
4 volatile怎么保证有序性和可见性
https://www.cnblogs.com/jthr/p/15969561.html 第五段
4.1 volatile保证可见性示例
4.1.1 示例1 不加volatile
public class VolatileTest2 {
static boolean flag = true; //不加volatile,没有可见性
public static void main(String[] args) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
while (flag) {
}
System.out.println(Thread.currentThread().getName() + "\t flag被修改为false,退出.....");
}, "t1").start();
//暂停2秒钟后让main线程修改flag值
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = false;
System.out.println("main线程修改完成");
}
}
执行结果,在flag被修改为true后
System.out.println(Thread.currentThread().getName() + "\t flag被修改为false,退出.....");这段代码还是没有执行,还在循环里面没有出来
说明flag被主线程修改为false,线程t1并不知道。
1) 主线程修改了flag之后没有将其刷新到主内存,所以t1线程看不到。
2) 主线程将flag刷新到了主内存,但是t1一直读取的是自己工作内存中flag的值,没有去主内存中更新获取flag最新的值
t1 come in
main线程修改完成
4.1.2 示例2,加上volatile
public class VolatileTest2 {
//static boolean flag = true; //不加volatile,没有可见性
static volatile boolean flag = true;
public static void main(String[] args) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
while (flag) {
}
System.out.println(Thread.currentThread().getName() + "\t flag被修改为false,退出.....");
}, "t1").start();
//暂停2秒钟后让main线程修改flag值
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = false;
System.out.println("main线程修改完成");
}
}
执行结果,flag被修改为false,退出.....打印出来了,说明主线程对flag的修改对线程t1可见。
1) 主线程修改了flag之后立即将其刷新到主内存
2)t1一直读取是去主内存中更新获取flag最新的值
t1 come in
main线程修改完成
t1 flag被修改为false,退出.....
4.2 volatile保证有序性示例
4.2.1 示例1 不加volatile
public class VolatileTest3 {
public static int x,y,a,b;
public static void main(String[] args) throws InterruptedException {
for (long i = 0;i < Long.MAX_VALUE;i++){
CountDownLatch latch = new CountDownLatch(2);
x = 0;
y = 0;
a = 0;
b = 0;
Thread t1 = new Thread(()->{
a = 1;
x = b;
latch.countDown();
});
Thread t2 =new Thread(()->{
b = 1;
y = a;
latch.countDown();
});
t1.start();
t2.start();
latch.await();
if(x == 0 && y == 0){
System.out.println("第" + i + "次执行");
break;
}
}
}
}
执行结果。若是不发生指令重排,那么就不可能出现x=0,y=0的情况。但是出现了,说明发生了指令重排
第65701次执行
4.2.2 加上volatile
public class VolatileTest3 {
public static volatile int x,y,a,b;
public static void main(String[] args) throws InterruptedException {
for (long i = 0;i < Long.MAX_VALUE;i++){
CountDownLatch latch = new CountDownLatch(2);
x = 0;
y = 0;
a = 0;
b = 0;
Thread t1 = new Thread(()->{
a = 1;
x = b;
latch.countDown();
});
Thread t2 =new Thread(()->{
b = 1;
y = a;
latch.countDown();
});
t1.start();
t2.start();
latch.await();
if(x == 0 && y == 0){
System.out.println("第" + i + "次执行");
break;
}
}
}
}
执行结果,发现执行很久,也没有出现x=0,y=0的情况,说明volatile保证了有序性
5 为什么volatile没有保证原子性
5.1 读取赋值一个普通变量的过程
由于上述的八条指令只能保证单条指令的原子性,不能保证多条指令共同执行时的原子性。
如下图,主内存存在一个变量A
线程一对A执行执行一系列的操作read、load、、、store、write
在任意两个操作中间,线程二都有可以对A执行一系列的操作read、load、、、store、write。

5.2 读取赋值一个volatile变量的过程
被volatile修饰的变量保证了read-load-use这三个操作(保证每次使用都是获取的主内存最新的值)的原子性和assign-store-write的原子性(保证每次写完立即把值刷回主内存)
如下图,主内存存在一个变量A
线程一对A执行执行一系列的操作read-load-use、assign-store-write
在read-load-use这三个操作和assign-store-write这三个操作中间,第二个线程有可以对A执行一系列的操作read-load-use、assign-store-write,所以不能保证原子性

5.3 示例
public class VolatileTest1 {
//不能保证原子性演示
private static volatile int a = 0;
public static void main(String[] args) throws InterruptedException {
CountDownLatch c = new CountDownLatch(10);
for (int i = 0;i < 10;i++) {
new Thread(()->{
for (int j = 0;j < 1000;j++) {
++a;
}
c.countDown();
}).start();
}
c.await();
System.out.println(a);
}
}
执行结果,期待结果是10000,结果不到10000,没有保证原子性
9789
线程基础知识10-volatile的更多相关文章
- Java__线程---基础知识全面实战---坦克大战系列为例
今天想将自己去年自己编写的坦克大战的代码与大家分享一下,主要面向学习过java但对java运用并不是很熟悉的同学,该编程代码基本上涉及了java基础知识的各个方面,大家可以通过练习该程序对自己的jav ...
- java线程基础知识----线程与锁
我们上一章已经谈到java线程的基础知识,我们学习了Thread的基础知识,今天我们开始学习java线程和锁. 1. 首先我们应该了解一下Object类的一些性质以其方法,首先我们知道Object类的 ...
- java线程基础知识----线程基础知识
不知道从什么时候开始,学习知识变成了一个短期记忆的过程,总是容易忘记自己当初学懂的知识(fuck!),不知道是自己没有经常使用还是当初理解的不够深入.今天准备再对java的线程进行一下系统的学习,希望 ...
- Windows核心编程 第六章 线程基础知识 (上)
第6章 线程的基础知识 理解线程是非常关键的,因为每个进程至少需要一个线程.本章将更加详细地介绍线程的知识.尤其是要讲述进程与线程之间存在多大的差别,它们各自具有什么作用.还要介绍系统如何使用线程内核 ...
- Java线程基础知识(状态、共享与协作)
1.基础概念 CPU核心数和线程数的关系 核心数:线程数=1:1 ;使用了超线程技术后---> 1:2 CPU时间片轮转机制 又称RR调度,会导致上下文切换 什么是进程和线程 进程:程序运行资源 ...
- Java并发之线程管理(线程基础知识)
因为书中涵盖的知识点比较全,所以就以书中的目录来学习和记录.当然,学习书中知识的时候自己的思考和实践是最重要的.说到线程,脑子里大概知道是个什么东西,但很多东西都还是懵懵懂懂,这是最可怕的.所以想着细 ...
- java线程基础知识----java daemon线程
java线程是一个运用很广泛的重点知识,我们很有必要了解java的daemon线程. 1.首先我们必须清楚的认识到java的线程分为两类: 用户线程和daemon线程 A. 用户线程: 用户线程可以简 ...
- java并发编程(一)----线程基础知识
在任何的生产环境中我们都不可逃避并发这个问题,多线程作为并发问题的技术支持让我们不得不去了解.这一块知识就像一个大蛋糕一样等着我们去分享,抱着学习的心态,记录下自己对并发的认识. 1.线程的状态: 线 ...
- Java并发编程(一):线程基础知识以及synchronized关键字
1.线程与多线程的概念:在一个程序中,能够独立运行的程序片段叫作“线程”(Thread).多线程(multithreading)是指从软件或者硬件上实现多个线程并发执行的技术. 2.多线程的意义:多线 ...
- 深度学习FPGA实现基础知识10(Deep Learning(深度学习)卷积神经网络(Convolutional Neural Network,CNN))
需求说明:深度学习FPGA实现知识储备 来自:http://blog.csdn.net/stdcoutzyx/article/details/41596663 说明:图文并茂,言简意赅. 自今年七月份 ...
随机推荐
- C#11之原始字符串
最近.NET7.0和C#11相继发布,笔者也是第一时间就用上了C#11,其中C#11的有一个更新能解决困扰我多年的问题,也就是文章的标题原始字符串. 在使用C#11的原始字符串时,发现的一些有意思的东 ...
- Go语言核心36讲16----接口
你好,我是郝林,今天我们来聊聊接口的相关内容. 前导内容:正确使用接口的基础知识 在Go语言的语境中,当我们在谈论"接口"的时候,一定指的是接口类型.因为接口类型与其他数据类型不同 ...
- DRF认证流程及源码分析
认证 前言 用户验证用户是否合法登陆. 部分内容在DRF视图的使用及源码流程分析讲解,建议先看讲解视图的这篇文章. 使用流程 认证使用的方法流程如下: 自定义认证类,继承BaseAuthenticat ...
- day24 JDBC批处理(通用泛型查询方法 & 下划线转驼峰命名法)
批处理 public static Integer addBatch(String[] sqls){ init(); try { //设置关闭自动提交 conn.setAutoCommit(false ...
- 【collection】1.java容器之HashMap&LinkedHashMap&Hashtable
Map源码剖析 HashMap&LinkedHashMap&Hashtable hashMap默认的阈值是0.75 HashMap put操作 put操作涉及3种结构,普通node节点 ...
- 【数据库】E-R图相关知识、手动自动绘制方法及工具推荐
一.知识 1.介绍 E-R图也称实体-联系图(Entity Relationship Diagram),提供了表示实体.属性和联系的方法,用来描述现实世界的概念模型. 2.组成 (1)实体(Entit ...
- ArcObjects SDK开发 014 MapSurround和普通Element
1.如何获取MapSurround 和获取MapFrame类似,如果你已经获取指北针.比例尺等对象,可以通过IGraphicsContainer的FindFrame函数获取.如果没有,则通过IGrap ...
- day37-文件上传和下载
文件上传下载 1.基本介绍 在Web应用中,文件上传和下载是非常常见的功能 如果是传输大文件一般用专门的工具或者插件 文件上传和下载需要用到两个包:commons-fileupload.jar和com ...
- 前端(js部分讲解)
BOM操作 BOM概念 BOM:Browser Object Model 是浏览器对象模型,浏览器对象模型提供了独立与内容的.可以与浏览器窗口进行互动的对象结构,BOM由多个对象构成,其中代表浏览器窗 ...
- VUE项目无法启动NODE版本与NODE-SASS、SASS-LOADER版本不兼容解决方案
一.错误分析 在VUE项目开发中,我们经常会遇到报错: Node Sass version 7.0.1 is incompatible with ^4.0.0. 网上解决方案也千奇百怪,最终操作下来, ...