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的更多相关文章

  1. Java__线程---基础知识全面实战---坦克大战系列为例

    今天想将自己去年自己编写的坦克大战的代码与大家分享一下,主要面向学习过java但对java运用并不是很熟悉的同学,该编程代码基本上涉及了java基础知识的各个方面,大家可以通过练习该程序对自己的jav ...

  2. java线程基础知识----线程与锁

    我们上一章已经谈到java线程的基础知识,我们学习了Thread的基础知识,今天我们开始学习java线程和锁. 1. 首先我们应该了解一下Object类的一些性质以其方法,首先我们知道Object类的 ...

  3. java线程基础知识----线程基础知识

    不知道从什么时候开始,学习知识变成了一个短期记忆的过程,总是容易忘记自己当初学懂的知识(fuck!),不知道是自己没有经常使用还是当初理解的不够深入.今天准备再对java的线程进行一下系统的学习,希望 ...

  4. Windows核心编程 第六章 线程基础知识 (上)

    第6章 线程的基础知识 理解线程是非常关键的,因为每个进程至少需要一个线程.本章将更加详细地介绍线程的知识.尤其是要讲述进程与线程之间存在多大的差别,它们各自具有什么作用.还要介绍系统如何使用线程内核 ...

  5. Java线程基础知识(状态、共享与协作)

    1.基础概念 CPU核心数和线程数的关系 核心数:线程数=1:1 ;使用了超线程技术后---> 1:2 CPU时间片轮转机制 又称RR调度,会导致上下文切换 什么是进程和线程 进程:程序运行资源 ...

  6. Java并发之线程管理(线程基础知识)

    因为书中涵盖的知识点比较全,所以就以书中的目录来学习和记录.当然,学习书中知识的时候自己的思考和实践是最重要的.说到线程,脑子里大概知道是个什么东西,但很多东西都还是懵懵懂懂,这是最可怕的.所以想着细 ...

  7. java线程基础知识----java daemon线程

    java线程是一个运用很广泛的重点知识,我们很有必要了解java的daemon线程. 1.首先我们必须清楚的认识到java的线程分为两类: 用户线程和daemon线程 A. 用户线程: 用户线程可以简 ...

  8. java并发编程(一)----线程基础知识

    在任何的生产环境中我们都不可逃避并发这个问题,多线程作为并发问题的技术支持让我们不得不去了解.这一块知识就像一个大蛋糕一样等着我们去分享,抱着学习的心态,记录下自己对并发的认识. 1.线程的状态: 线 ...

  9. Java并发编程(一):线程基础知识以及synchronized关键字

    1.线程与多线程的概念:在一个程序中,能够独立运行的程序片段叫作“线程”(Thread).多线程(multithreading)是指从软件或者硬件上实现多个线程并发执行的技术. 2.多线程的意义:多线 ...

  10. 深度学习FPGA实现基础知识10(Deep Learning(深度学习)卷积神经网络(Convolutional Neural Network,CNN))

    需求说明:深度学习FPGA实现知识储备 来自:http://blog.csdn.net/stdcoutzyx/article/details/41596663 说明:图文并茂,言简意赅. 自今年七月份 ...

随机推荐

  1. 一次SpringBoot版本升级,引发的血案

    前言 最近项目组升级了SpringBoot版本,由之前的2.0.4升级到最新版本2.7.5,却引出了一个大Bug. 到底是怎么回事呢? 1.案发现场 有一天,项目组的同事反馈给我说,我之前有个接口在新 ...

  2. ui自动化测试数据复原遇到的坑——2、python连接informix时pytest报致命错误Windows fatal exception: access violation

    python连接informix只能通过jdbc(需要先部署java环境.我试过到IBM上下载ODBC但结局是失败的),在执行pytest时发现有一串报错(大致是下面的这样): Windows fat ...

  3. [leetcode] 547. Number of Provinces

    题目 There are n cities. Some of them are connected, while some are not. If city a is connected direct ...

  4. vcenter密码设置永不过期

    由于机房断电,原本的vcenter重启后web页面出现报错,为尽快恢复vcenter管理机,直接停用了旧的vcenter虚机,重新安装了一台vcenter,两个月后,页面开始报警密码即将到期: 经查阅 ...

  5. 第2-3-6章 打包批量下载附件的接口开发-文件存储服务系统-nginx/fastDFS/minio/阿里云oss/七牛云oss

    目录 5.6 接口开发-根据文件id打包下载附件 5.6.1 接口文档 5.6.2 代码实现 5.6.3 接口测试 5.7 接口开发-根据业务类型/业务id打包下载 5.7.1 接口文档 5.7.2 ...

  6. 内核常用的might_sleep函数

    1. 前言 内核版本:linux 4.9.225.内核版本:linux 4.9.225.对于内核常用的might_sleep函数,如果没有调试的需要(没有定义CONFIG_DEBUG_ATOMIC_S ...

  7. MIT6.828学习笔记1

    Lab 1: Booting a PC Part 1: PC Bootstrap The PC's Physical Address Space 早期的PC机基于Intel的8088处理器,能够寻址1 ...

  8. maven 项目依赖自动导入失败(pom.xml 文件爆红),解决--手动导入

    idea 报错信息提示:Dependency 'xxx' not found 解决方法:可以通过更换仓库的镜像配置解决,但是一般咱都在配置maven的时候,设置成阿里云仓库镜像了,更换成其他的,可能出 ...

  9. 2022i春秋-冬季赛nan’s analysis

    下载附件,在tcp的0流可以看到一个keyisChunqiuGame00504 在3流处得到压缩包,密码不是上面那个,后面对附件找了几个小时都没发现压缩包密码,我是笨比.后面才看到有在线环境. 然后找 ...

  10. SAP-FI模块 如何处理自动生成会计凭证增强

    一. 相关问题概览 1. 固定资产业务过渡科目摘要增强功能-F-02     需用表BKPF.BSEG.T001.通过BUKRS.BELNR.GJAHR三个字段相等关联BKPF与BSEG.通过BKPF ...