问题背景:

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

以下边的代码为例:

package com.dx.juc;public class VoltileTest {public static void main(String[] args) {MyThread thread=new MyThread();thread.start();while (true){if(thread.flag){System.out.println("thread flag is true");break;}}System.out.println("complete");}
}class MyThread extends Thread {public  boolean flag = false;@Overridepublic void run() {try {Thread.sleep(200);flag = true;System.out.println("flag is changed;" + flag);} catch (InterruptedException ex) {ex.printStackTrace();}}
}

在线程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实现内存可见性

package com.dx.juc;public class VoltileTest {public static void main(String[] args) {MyThread thread = new MyThread();thread.start();while (true) {if (thread.isFlag()) {System.out.println("thread flag is true");break;}}System.out.println("complete");}
}class MyThread extends Thread {private volatile boolean flag = false;@Overridepublic void run() {try {Thread.sleep(200);setFlag(true) ;System.out.println("flag is changed;" + isFlag());} catch (InterruptedException ex) {ex.printStackTrace();}}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}
}

使用了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读取到的就是最新的正确的值。

转载于:https://www.cnblogs.com/yy3b2007com/p/8890673.html

Java-JUC(一):volatile引入相关推荐

  1. Java中的Volatile如何工作? Java中的volatile关键字示例

    如何在Java中使用Volatile关键字 在Java采访中,什么是volatile变量以及何时在Java中使用volatile变量是Java 采访中一个著名的多线程采访问题 . 尽管许多程序员都知道 ...

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

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

  3. Java JUC学习 - ConcurrentLinkedDeque 详解

    Java JUC学习 - ConcurrentLinkedDeque 详解 0x00 前言 如何实现并发程序,对于Java以及其他高级语言来说都是一件并不容易的事情.在大一上学期的时候,我们学习了链表 ...

  4. Java基础:volatile详解

    Java基础:volatile详解 1.volatile保证可见性 1.1.什么是JMM模型? 1.2.volatile保证可见性的代码验证 1.2.1.无可见性代码验证 1.2.1.volatile ...

  5. Java JUC并发编程详解

    Java JUC并发编程详解 1. JUC概述 1.1 JUC简介 1.2 进程与线程 1.2 并发与并行 1.3 用户线程和守护线程 2. Lock接口 2.1 Synchronized 2.2 什 ...

  6. Java juc系列6 —— 线程池

    Java JUC系列目录链接 Java 线程池核心原理解析 Java线程池的基础用法 创建和使用 为什么需要线程池 线程的生命周期[^1] 新建 就绪 运行 休眠 终止 使用线程的代价 线程池帮我们做 ...

  7. Java JUC系列

    1.Java JUC 简介 2.volatile 关键字-内存可见性 3.原子变量 CAS算法 4.ConcurrentHashMap 锁分段机制 5.CountDownLatch 闭锁 6.实现 C ...

  8. 面试:说说Java中的 volatile 关键词?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | Matrix海子 来源 | https://w ...

  9. null在java存在的意义何在,Java并发编程——volatile关键字

    一.volatile是什么 volatile是Java并发编程中重要的一个关键字,被比喻为"轻量级的synchronized",与synchronized不同的是,volatile ...

最新文章

  1. python怎么读文件名-Python获取指定文件夹下的文件名
  2. python声明编码作用_Python源代码中的编码声明字符串的作用
  3. TT 安装 之 AIX
  4. 2022将至,前端程序员们应该一起放个烟花庆祝一下,走起
  5. Maven详解及实例
  6. linux c 网络编程与信号量,linux网络编程-----线程同步--信号量
  7. 二 、 搭建Android 开发环境读书笔记
  8. php 将url转成伪静态,php兑现url伪静态化,全过程详解
  9. 分布式CAP是什么?
  10. 华为服务器bios系统,华为服务器bios设置
  11. python快速实现数字华容道小游戏
  12. 【创意生活】铅笔实景画,绝对创意
  13. win10进入安全模式和退出安全模式
  14. 五、交换机 与 发布/订阅模式、路由模式、主题模式
  15. 电磁场与仿真软件(22)
  16. python编程练习--水仙花数
  17. Greenplum 实时数据仓库实践(6)——实时数据装载
  18. matlab 统计数组频数_matlab统计 频数、频率和累积频率
  19. 医院信息管理系统论文java_毕业论文-基于java的医院门诊信息管理系统设计与实现...
  20. 【触摸屏功能测试】昆仑通态MCGS——物联网功能测试

热门文章

  1. poj 1753 Flip Game dfs 技巧
  2. Python之黏包的解决
  3. BootStrap学习笔记,优缺点总结
  4. Android的NDK开发(3)————JNI数据类型的详解
  5. 在OpenCV下写的直方图匹配(直方图规定化)C++源码!
  6. unity3d 数学基础与数学辅助类
  7. 从ubuntu中文论坛转载的一片超好的文章,慢慢学习中
  8. javascript写各种排序算法
  9. springboot 事务手动回滚_Spring Boot中的事务是如何实现的
  10. linux自动执行top,Linux top 命令使用