一、前言

    volatile的关键词的使用在JVM内存模型中已是老生常谈了,这篇文章主要结合自己对可见性的一些认识和一些直观的例子来谈谈volatile。文章正文大致分为三部分,首先会介绍一下happen-before,接着讲解volatile的一些使用场景,最后会附上一些例子来论证使用与不使用volatile的区别。

二、happen-before

    对操作系统有认识的同学一定知道,CPU一般有三级缓存,在与内存交互的时候,存在缓存与内存的更新问题,其次CPU在读取指令的时候,会做一些指令重排序的工作,提高程序运行效率。类比JVM内存模型(见下图),每个线程拥有自己的工作内存,同时存在一个主存,线程间通过主存来进行通信,同样的,JVM也存在指令重排序,可见JVM内存模型与实际物理内存模型十分相似。(这里顺便提一下,编译器其实也会作一定重排序优化)。

    

    作为开发人员,你不可能了解到每个JVM优化细节,更不可能了解到CPU何时会进行指令重排序,所以java语言定义了更上层的一个概念,就是"happen-before"。起初,我看到这个单词的时候,误以为这是一个指令执行顺序的规则,后来仔细想想又发觉不对劲。如果”happen-before“仅仅是抽象了指令执行顺序的概念,那么它就把握不了“工作内存将值写回主存”和“工作内存从主存中刷新自己的值”这个两个action的时机。那么这个概念也就变得没什么意义了。所以!所以!所以!”happen-before“是一个可见性的原则!!!

下面给出happen-before的具体规则:

程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;

    

三、volatile的使用场景

    happen-before的第三条规则提到“volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作”,也就是说;一个volatile变量的写操作对后续对读操作可见。说白了就是每次写完volatile变量,都会将值从工作内存写回到主存中去,每次读取volatile变量,工作内存必须从主存中刷新下自己的值。如此的话,volatile就是为了解决多个线程共享数据的可见性问题。但是不是任何数据共享场景都可以使用volatile,必须满足以下两种情景才行。

    应用场景:

    1.多个线程不依赖原值的情况下进行读写操作

    2.一个线程依赖原值进行写操作,多个线程进行读操作

在我看来,除了这两种情况外,无非是多个线程依赖原值进行运算,这样子倒不是说volatile可见性不起作用了,而是无法保证读取原值和运算是一个原子操作!举个简单的例子,多个线程执行i++;i是一个共享变量,由于读取i的值和i自增不是一个原子操作,所以i最终会丢失掉一部分自增过程。代码如下,最终i输出的结果是一个小于1000的整数。

/**
* Created by chenqimiao on 17/8/23.
*/
public class Testv {
public static volatile int i = 0;
public static void main(String args[]){
for (int i =0;i<1000;i++){
new Thread(){
public void run(){
Thread.yield();
Testv.i++;
}
}.start();
}
System.out.println(Testv.i); }
}

要满足以上这种需求,我们还必须赋予代码原子性,最常用的肯定是锁操作了,一个字稳,性能可观,同时保证原子性和可见性。如果想操作一波的话,还可以考虑使用一些无锁操作,如CAS,象java.util.concurrent包下的一些原子类就是利用了CAS来做到原子性,但原子性并不能保证可见性,这个时候,还需要配合volatile。

以上种种都是对volatile使用场景的概括,想了解具体的使用场景可以参考博文:https://www.ibm.com/developerworks/cn/java/j-jtp06197.html

四、volatile可见性的证明

   先上段代码好了,不知道从何说起了。

  

package com.example.demo.netty;

/**
* Created with IntelliJ IDEA.
* User: chenqimiao
* Date: 2017/8/23
* Time: 9:16
* To change this template use File | Settings | File Templates.
*/
public class VolatileTest { boolean isStop = false; public void test(){
Thread t1 = new Thread(){
public void run() {
isStop=true;
}
};
Thread t2 = new Thread(){
public void run() {
while (!isStop);
}
}; t2.start();
t1.start(); }
public static void main(String args[]) throws InterruptedException {
for (int i =0;i<25;i++){
new VolatileTest().test();
} }
}

    上面这段代码可能永远也不会结束,因为线程一对isStop的赋值,线程二可能对此并不可见。当然只是可能,所以为了放大可见性问题,我这里作了25次循环。只要有一组线程,“线程一对isStop的赋值,线程二对此不可见”的情况发生,就不会退出程序。

    now,假如你给 isStop 添加一个 volatile 关键字,那么你会发现程序立马就会退出。

volatile可见性的一些认识的更多相关文章

  1. volatile可见性的一些认识和论证

    一.前言 volatile的关键词的使用在JVM内存模型中已是老生常谈了,这篇文章主要结合自己对可见性的一些认识和一些直观的例子来谈谈volatile.文章正文大致分为三部分,首先会介绍一下happe ...

  2. Java并发编程-volatile可见性的介绍

    要学习好Java的多线程,就一定得对volatile关键字的作用机制了熟于胸.最近博主看了大量关于volatile的相关博客,对其有了一点初步的理解和认识,下面通过自己的话叙述整理一遍. 有什么用? ...

  3. volatile可见性案例-黑马

    volatile可见性案例-黑马 package com.mozq.demo.demo; class Task implements Runnable{ //public boolean flag = ...

  4. Volatile可见性 与 Synchronization原子性的优化

    Volatile可见性 比如现在我们有这样一段代码:线程等待另一个线程将数据装载完就输出success,可是最后程序一直卡在while循环里没有往下执行. public class VolatileD ...

  5. volatile可见性和指令重排

    volatile关键字的2个作用 1.线程的可见性 2.防止指令重排 什么是线程的可见性? 线程的可见性 就是一个线程对一个变量进行更改操作 其他线程获取会获得最新的值. 线程在执行的行 操作主线程的 ...

  6. Volatile可见性分析(一)

    JUC(java.util.concurrent) 进程和线程 进程:后台运行的程序(我们打开的一个软件,就是进程) 线程:轻量级的进程,并且一个进程包含多个线程(同在一个软件内,同时运行窗口,就是线 ...

  7. java synchronized实现可见性对比volatile

    问题: 大家可以先看看这个问题,看看这个是否有问题呢? 那里有问题呢? public class ThreadSafeCache { int result; public int getResult( ...

  8. Java内存模型JMM与可见性

    Java内存模型JMM与可见性 标签(空格分隔): java 1 何为JMM JMM:通俗地讲,就是描述Java中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这 ...

  9. Java并发编程--Volatile详解

    摘要      Volatile是Java提供的一种弱同步机制,当一个变量被声明成volatile类型后编译器不会将该变量的操作与其他内存操作进行重排序.在某些场景下使用volatile代替锁可以减少 ...

随机推荐

  1. es6的一些知识点

    es6的一些知识点 前言:es6(ECMAscript2015)标准 let.const.var的一些区别 let.const 块级作用域.全局作用域.函数作用域 var 全局作用域.函数作用域 变量 ...

  2. 基于JQ的单双日历,本人自己写的哈,还没封装,但是也能用

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>小 ...

  3. CJOJ 1976 二叉苹果树 / URAL 1018 Binary Apple Tree(树型动态规划)

    CJOJ 1976 二叉苹果树 / URAL 1018 Binary Apple Tree(树型动态规划) Description 有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的 ...

  4. Spring和SpringMVC父子的容器之道---[上篇]

    Spring和SpringMVC作为Bean管理容器和MVC层的默认框架,已被众多WEB应用采用,而在实际开发中,由于有了强大的注解功能,很多基于XML的配置方式已经被替代,但在实际项目中,我们经常会 ...

  5. Linux常用命令及shell技巧

    这里列出一些个人在工作中常使用的各种linux命令,每一个不详细讲参数,只写经常用的参数.希望快速获得在linux命令行工作的能力的朋友可以看看.本人一直觉的,不使用linux 图形界面,以xshel ...

  6. 如是使用JS实现页面内容随机显示

    之前有个客户咨询我,因为他们公司的业务员有多个人,但公司网站的联系方式板块里只够放一个人的信息,所以就想能不能实现这个联系方式信息随机显示,对于业务或客服人员来说也能做到分配均匀公平.本文我们将和大家 ...

  7. python数据结构之队列

    队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表. 队列是一种先进先出的(First In First Out)的线性表,简称FIFO.允许插入的一端为队尾,允许删除的一端 ...

  8. (转)IBM MQ 创建以及常见问题集锦

    背景:这篇文章还是很全面的,但是很杂乱 后面慢慢整理吧! 1 MQ 消息队列+发送队列+消息通道 接收通道名称与发送端的发送通道名称要一致,修改通道信息后要执行 start channle(chlna ...

  9. (转)Synchronized(对象锁)和Static Synchronized(类锁)的区别

    场景:面试的时候经常用得到! 1 综述 Synchronized和Static Synchronized区别 一个是实例锁(锁在某一个实例对象上,如果该类是单例,那么该锁也具有全局锁的概念),一个是全 ...

  10. [COGS 1752] 摩基亚Mokia

    照例先上题面 1752. [BOI2007]摩基亚Mokia 输入文件:mokia.in   输出文件:mokia.out 时间限制:1.5 s   内存限制:128 MB [题目描述] 摩尔瓦多的移 ...