问题背景:

volatile是为了解决内存可见性而生的,什么是内存不可见性呢?

以下边的代码为例:

  1. package com.dx.juc;
  2.  
  3. public class VoltileTest {
  4. public static void main(String[] args) {
  5. MyThread thread=new MyThread();
  6. thread.start();
  7.  
  8. while (true){
  9. if(thread.flag){
  10. System.out.println("thread flag is true");
  11. break;
  12. }
  13. }
  14.  
  15. System.out.println("complete");
  16. }
  17. }
  18.  
  19. class MyThread extends Thread {
  20. public boolean flag = false;
  21.  
  22. @Override
  23. public void run() {
  24. try {
  25. Thread.sleep(200);
  26. flag = true;
  27. System.out.println("flag is changed;" + flag);
  28. } catch (InterruptedException ex) {
  29. ex.printStackTrace();
  30. }
  31. }
  32. }

在线程thread开始执行的过程中会吧thread.flag属性值修改为true,一般情况下来说,main线程在while(true)循环内部是可以检测到thread.flag被修改了,而且我们希望是这样子。但是程序运行起来的时候会发现flag在线程thread中被修改后,main线程并不能读取到被修改的值。

输出结果为:

此时,就是main一直在执行while(true)循环操作。

出现问题的原因?

原因:

1)thread线程修改flag值时间晚于main获取(拷贝)flag值(到main缓存)的时间;

2)同时main线程中读取flag数据是从main线程的缓存中读取,而不是直接从主存中读取。

或用更简洁的话来描述:

两个线程操作共享数据时,彼此不可见,线程可见性导致的问题。

那么为什么要使用缓存?

  • Register

寄存器是CPU的内部组成单元,是CPU运算时取指令和数据的地方,速度很快,寄存器可以用来暂存指令、数据和地址。在CPU中,通常有通用寄存器,如指令寄存器IR;特殊功能寄存器,如程序计数器PC、sp等

  • 寄存器的工作方式很简单,只有两步:(1)找到相关的位,(2)读取这些位。
  • Cache

缓存即就是用于暂时存放内存中的数据,若果寄存器要取内存中的一部分数据时,可直接从缓存中取到,这样可以调高速度。高速缓存是内存的部分拷贝。

  • 内存的工作方式就要复杂得多:

(1)找到数据的指针。(指针可能存放在寄存器内,所以这一步就已经包括寄存器的全部工作了。)

(2)将指针送往内存管理单元(MMU),由MMU将虚拟的内存地址翻译成实际的物理地址。

(3)将物理地址送往内存控制器(memory controller),由内存控制器找出该地址在哪一根内存插槽(bank)上。

(4)确定数据在哪一个内存块(chunk)上,从该块读取数据。

(5)数据先送回内存控制器,再送回CPU,然后开始使用。

内存的工作流程比寄存器多出许多步。每一步都会产生延迟,累积起来就使得内存比寄存器慢得多。

为了缓解寄存器与内存之间的巨大速度差异,硬件设计师做出了许多努力,包括在CPU内部设置缓存、优化CPU工作方式,尽量一次性从内存读取指令所要用到的全部数据等等。

如何解决内存不可见的问题?使用volatile

使用java中的volatile实现内存可见性

  1. package com.dx.juc;
  2.  
  3. public class VoltileTest {
  4. public static void main(String[] args) {
  5. MyThread thread = new MyThread();
  6. thread.start();
  7.  
  8. while (true) {
  9. if (thread.isFlag()) {
  10. System.out.println("thread flag is true");
  11. break;
  12. }
  13. }
  14.  
  15. System.out.println("complete");
  16. }
  17. }
  18.  
  19. class MyThread extends Thread {
  20. private volatile boolean flag = false;
  21.  
  22. @Override
  23. public void run() {
  24. try {
  25. Thread.sleep(200);
  26. setFlag(true) ;
  27. System.out.println("flag is changed;" + isFlag());
  28. } catch (InterruptedException ex) {
  29. ex.printStackTrace();
  30. }
  31. }
  32.  
  33. public boolean isFlag() {
  34. return flag;
  35. }
  36.  
  37. public void setFlag(boolean flag) {
  38. this.flag = flag;
  39. }
  40. }

使用了volatile后确实可以保证了内存可见性,当thread线程修改了flag的值时,会先把thread线程缓存中的值修改,立即把thread线程缓存中修改的值刷新到主存中,同时引用了被volatile修饰的变量所在的线程对应缓存(清空)失效,此时main线程在while循环处理是检测到缓存中无该变量值,则要从主存中获取flag对象,因此,确保了数据的可见性。

用volatile修饰之后带来的影响:

第一:使用volatile关键字会强制将修改的值立即写入主存;

第二:使用volatile关键字的话,当线程thread进行修改时,会导致线程main的工作内存中缓存变量flag的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);

第三:由于线程main的工作内存中缓存变量flag的缓存行无效,所以线程main再次读取变量flag的值时会去主存读取。

那么在线程thread修改flag值时(当然这里包括2个操作,修改线程thread工作内存中的值,然后将修改后的值写入内存),会使得线程main的工作内存中缓存变量flag的缓存行无效,然后线程main读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。

那么线程main读取到的就是最新的正确的值。

参考《Java内存模型-volatile的内存语义

原 Java并发(10)-JUC线程池 Executor框架

原 Java并发(9)--JUC并发工具类:CountDownLatch、Semaphore、CyclicBarrier、Exchanger

原 Java并发(8)--JUC之同步队列器AQS原理、重入锁ReentrantLock、读写锁ReentrantReadWriteLock

原 Java并发(7)--并发容器 J.U.C:Java.util.concurrency

原 Java并发(6)--线程安全策略:不可变对象、ThreadLocal、常见的线程安全与线程不安全类、同步容器

原 Java并发(5)--线程安全发布对象:懒汉模式、饿汉模式

原 Java并发(4)--线程安全:原子性(Atomic)、可见性、有序性

原 Java并发(3)--项目准备:环境初始化、案例准备、并发模拟工具、并发模拟代码

原 Java并发(2)--并发基本:线程安全、锁优化

原 Java并发(1)--并发基本:CPU缓存、Java内存模型、Java线程

BAT面试题 26篇->原 最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁

技术探索 - 慕课网实战-高并发

透彻理解Java并发编程

Java-JUC(一):volatile引入的更多相关文章

  1. Java单例模式和volatile关键字

    单例模式是最简单的设计模式,实现也非常"简单".一直以为我写没有问题,直到被 Coverity 打脸. 1. 暴露问题 前段时间,有段代码被 Coverity 警告了,简化一下代码 ...

  2. Java中的volatile关键字的功能

    Java中的volatile关键字的功能 volatile是java中的一个类型修饰符.它是被设计用来修饰被不同线程访问和修改的变量.如果不加入volatile,基本上会导致这样的结果:要么无法编写多 ...

  3. 深入理解Java中的volatile关键字

    在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...

  4. Java核心复习—— volatile 与可见性

    一.介绍 volatile保证共享变量的"可见性".可见性指的是当一个线程修改变量时,另一个线程能读到这个修改的值. 这里就要提出几个问题. 问题1:为什么一个线程修改时,另一个线 ...

  5. java并发系列(六)-----Java并发:volatile关键字解析

    在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保证可见性 ...

  6. Java Juc学习笔记

    Java JUC 简介 在 Java 5.0 提供了 java.util.concurrent (简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括 ...

  7. Java JUC之Atomic系列12大类实例讲解和原理分解

    Java JUC之Atomic系列12大类实例讲解和原理分解 2013-02-21      0个评论       作者:xieyuooo 收藏    我要投稿 在java6以后我们不但接触到了Loc ...

  8. Java并发编程volatile关键字

    volatile理解 Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和volatile 关键字机制.volatile具有synchronized关键字的“可见性”,vo ...

  9. 一起来看看java并发中volatile关键字的神奇之处

    并发编程中的三个概念: 1.原子性 在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行. 2.可见性 对于可见性,Java提供了volati ...

随机推荐

  1. #pragma pack(n)的使用

    在缺省情况下,编译器为了让程序跑得跟快,减少CPU读取数据的指令周期,对结构体的存储进行了优化, 比如:如下结构体 struct s { char ch; int i; }; 虽然变量ch本身只有1个 ...

  2. 重读JavaScript高级程序设计

    不断更新中--- 第三章 基本概念 1.变量声明但未初始化值是undefined,而未声明的变量只能执行typeof操作,并且未初始化和未声明用typeof都同样返回undefined 2.Numbe ...

  3. 一些收集的MikroTik RouterOS破解版虚拟机VMware

    会不定期更新,也许后续自己来做,现在是收集.持续关注这个分享链接即可. 链接:https://pan.baidu.com/s/1j7ciesPm1yAgCJ26dLJ8sg  密码:i64w

  4. dp和px,那些不得不吐槽的故事——Android平台图片文字元素单位浅析 (转)

    一个优秀的手机软件,不仅要有精巧的功能,流畅的速度,让人赏心悦目的UI也往往是用户选择的重要理由.作为移动产品的PM,也需要了解一些在UI设计中的基本知识. 1. px和pt,一对好伙伴 在视觉设计中 ...

  5. Unity3D使用碰撞体做触发器实现简单的自己主动开门

     在游戏制作中触发器的使用很的方便也很有用. 这一张我们简介一下怎样使用一个简单的触发器来实现自己主动开门关门的效果. 首先确保你已经对门进行了动画的设置. 详细流程例如以下. 选择Window- ...

  6. STM32F103 TIM3定时器初始化程序

    //TIM3 分频 #define TIM3_DIV1 (1-1) #define TIM3_DIV18 (18-1) #define TIM3_DIV72 (72-1) //************ ...

  7. 现场故障-数据量超出plsql developer结果集导致应用程序无数据现象

    情景重现: 维护人员想要用plsql developer工具查看一年前某表的数据,表中数据约30W行,因为此时无业务,维护人员关闭了应用程序.查询时选择了将所有数据所有列出,结果在显示到3W多行时,弹 ...

  8. linux内核netfilter模块分析之:HOOKs点的注册及调用

    转自;http://blog.csdn.net/suiyuan19840208/article/details/19684883 -1: 为什么要写这个东西?最近在找工作,之前netfilter 这一 ...

  9. Linux为sh脚本文件添加执行权限

    chmod是权限管理命令change the permissions mode of a file的缩写..u代表所有者,x代表执行权限. + 表示增加权限.chmod u+x file.sh 就表示 ...

  10. 迷失第一季/全集Lost 1迅雷下载

    迷失 第一季 Lost Season 1 (2004)本季看点:影片主要讲述一架客机坠落在太平洋的孤岛上,48名乘客侥幸生还.面对这种荒芜人烟的小岛,他们如何才能生存下去呢?生还者形形色色,国籍.人种 ...