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. 嵌入式-C语言基础:快速选择排序实现从大到小排序

    #include<stdio.h> int main() { /*简单选择排序:从大到小:一共比较sizeArr-1轮,每一轮的第一个数是arr[i],第一个数依次和它后面的每个数比较*/ ...

  2. Atlas人工智能基础知识

    目录 一.  AI基本概念 1.人工智能是什么 2.人工智能.机器学习.深度学习的关系是什么 2.监督学习.无监督学习.半监督学习和强化学习是什么 3.什么是模型和网络 4.什么是训练和推理 5.什么 ...

  3. nginx转发到uwsgi的配置

    server{ server_name ; listen 80 default_server; add_header Access-Control-Allow-Origin *; add_header ...

  4. HDLBits答案——Circuits

    1 Combinational Logic 1.1 Basic Gates 1.1.1 Exams/m2014 q4h module top_module ( input in, output out ...

  5. C语言实验手册

    在三位整数(100~999)中寻找符合条件的整数,并以此从小到大存到数组当中,它既是完全平方数,又是两位数字相同,例如144,676等. #include<stdio.h> #includ ...

  6. c++ trivial, standard layout和POD类型解析

    目录 1. trivial类型 2. standard layout类型 3. 集大成者,POD(Plain Old Data)类型 4. 测试代码 1. trivial类型 占用一片连续的内存,编译 ...

  7. http 缓存 笔记

    http 缓存,有时候静态资源没更新的情况下,不需要每次都去服务器获取,减少资源的请求. Http 报文中与缓存相关的首部字段 1. 通用首部字段(就是请求报文和响应报文都能用上的字段) 2. 请求首 ...

  8. 搭建漏洞环境及实战——搭建DVWA漏洞环境

    DVWA是一款开源的渗透测试漏洞练习平台,其中内涵XSS.SQL注入.文件上传.文件包含.CSRF和暴力破解等各个难度的测试环境. 1.在安装时需要在数据库里创建一个数据库名,进入MySQL管理中的p ...

  9. Python面试常见算法题集锦(递归部分)

    0x1 前言 开始学习python基础的时候,有以下几种算法是面试中常见的,也是前期学习python的时候可以连带学习了解的,不卡门槛哟 0x2 实现算法的方式很多种,而算法的实现也是分程序语言的,此 ...

  10. jmeter json提取器提取某个属性的所有值

    json 提取器各字段说明: Variable names:保存的变量名,后面使用${Variable names}引用 JSON Path  expressions:调试通过的json path表达 ...