2019独角兽企业重金招聘Python工程师标准>>>

  首先并发编程有三大特性: 可见性,有序性,原子性。volatile关键字实现了前面两个特性。那么它是如何实现这两个特性的呢?

  首先是可见性。可见性主要是让缓存,直接写穿透到主存中。然后另外的cpu 通过底层的硬件层面的嗅探,可以发现自己cpu本地的缓存已经失效。然后到主存中直接读取。现在让我们来看看,cpu里面的缓存具体和主存如何交互。

  说道这里首先需要了解,计算机内部存储器结构。每个核中都有自己的通用寄存器,比如eax,ebx,edx,esi,esp.访问这些寄存器里面的内容,只要一个机器周期,就够了,通常小于1ns..然后是L1,L2的本地core的缓存。 通常在10个机器周期左右。大约10ns. L3级缓存是多core共享的。

以我们常见的X86芯片为例,Cache的结构下图所示:整个Cache被分为S个组,每个组是又由E行个最小的存储单元——Cache Line所组成,而一个Cache Line中有B(B=64)个字节用来存储数据,即每个Cache Line能存储64个字节的数据,每个Cache Line又额外包含一个有效位(valid bit)、t个标记位(tag bit),其中valid bit用来表示该缓存行是否有效;tag bit用来协助寻址,唯一标识存储在CacheLine中的块;而Cache Line里的64个字节其实是对应内存地址中的数据拷贝。根据Cache的结构题,我们可以推算出每一级Cache的大小为B×E×S。

1级大概是32k 或者32K X2  ,2级大概是 256K或者256KX2 ,L3一般是3M左右。当多线程并发访问一段代码的时候,读取变量到本地的core进行计算,然后把数据写入到缓存中,假如没有volatile关键字的话,缓存采用的是write back 策略,直接写到缓存,看如下代码,虽然启用了10个线程进行计数,但是打印出来的count值是0.即使sleep(100),100ms,等所有线程都起来了,也是得到的结果都是不定的,因为无法确定缓存什么时候,换出写到主存中。

1 public class VolatileTest {
2
3     private int count ;
4     public void increase() {
5         count++;
6     }
7     public void  getCount(){
8         System.out.println(count);
9     }
10     public static void main(String[] args) throws InterruptedException{
11         VolatileTest test =  new VolatileTest();
12         for(int i=0;i<10;i++){
13             new Thread(){
14                 @Override
15                 public void run() {
16                     for(int j=0;j<1000;j++)
17                         test.increase();
18                 }
19             }.start();
20         }
21         Thread.sleep(100);
22         test.getCount();
23     }
24 }

volatile 作用1 就是一个线程改变了共享变量的值,其它线程马上能看见,就是可见性。比如下面的这段代码。

 1 import java.util.concurrent.CountDownLatch; 2  3 public class VolatileTest2 { 4  5     private static volatile boolean status=false ; 6  7     private static CountDownLatch start  = new CountDownLatch(1); 8  9     public void setStatusTrue(){10         status =true;11     }12     public void  getStatus(){13         System.out.println(status);14     }15     public static void main(String[] args) throws InterruptedException{16         VolatileTest2 test =  new VolatileTest2();17         new Thread(new Task2(start,test)).start();18         for(int i=0;i<10;i++){19             new Thread(new Task1(start,test)).start();20         }21     }22 }23 24 class Task1 implements Runnable{25     private CountDownLatch latch;26     private VolatileTest2 test ;27     public Task1(CountDownLatch start,VolatileTest2 test){28         this.latch = start;29         this.test = test;30     }31     @Override32     public void run() {33         try{34            latch.await();35         }catch (Exception e){36         }37             test.getStatus();38     }39 }40 41 /**42  * 这个线程吧状态设置成true,然后同步计数器马上变成0.之后,就其它线程马上就能看到status状态为true43  */44 class Task2 implements Runnable{45     private CountDownLatch latch;46     private VolatileTest2 test ;47     public Task2(CountDownLatch start,VolatileTest2 test){48         this.latch = start;49         this.test = test;50     }51     @Override52     public void run() {53         test.setStatusTrue();54         latch.countDown();55         System.out.println("countDown===");56     }57 }

具体的原理,这里涉及到缓存一致性原理,MESI 协议

失效(Invalid)缓存段,要么已经不在缓存中,要么它的内容已经过时。为了达到缓存的目的,这种状态的段将会被忽略。一旦缓存段被标记为失效,那效果就等同于它从来没被加载到缓存中。

共享(Shared)缓存段,它是和主内存内容保持一致的一份拷贝,在这种状态下的缓存段只能被读取,不能被写入。多组缓存可以同时拥有针对同一内存地址的共享缓存段,这就是名称的由来。

独占(Exclusive)缓存段,和S状态一样,也是和主内存内容保持一致的一份拷贝。区别在于,如果一个处理器持有了某个E状态的缓存段,那其他处理器就不能同时持有它,所以叫“独占”。这意味着,如果其他处理器原本也持有同一缓存段,那么它会马上变成“失效”状态。

已修改(Modified)缓存段,属于脏段,它们已经被所属的处理器修改了。如果一个段处于已修改状态,那么它在其他处理器缓存中的拷贝马上会变成失效状态,这个规律和E状态一样。此外,已修改缓存段如果被丢弃或标记为失效,那么先要把它的内容回写到内存中——这和回写模式下常规的脏段处理方式一样。

在写入时锁定缓存,称为Exclusive状态,然后同时写入缓存和主存,当读取数据的时候,强行,从主存中读取,并且申请缓存行填充。

2 有序性,这个又如何保证呢?

《深入理解Java虚拟机》中有这句话“”“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”“”,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;至于什么是内存屏障,不做深入了解。只需要知道是CPU Out-of-order execution 和 compiler reordering optimizations。用于对内存操作的顺序限制。

Memory access instructions, such as loads and stores, typically take longer to execute than other instructions. Therefore, compilers use registers to hold frequently used values and processors use high speed caches to hold the most frequently used memory locations. Another common optimization is for compilers and processors to rearrange the order that instructions are executed so that the processor does not have to wait for memory accesses to complete. This can result in memory being accessed in a different order than specified in the source code. While this typically will not cause a problem in a single thread of execution, it can cause a problem if the location can also be accessed from another processor or device.

转载于:https://my.oschina.net/u/3980693/blog/2967178

Java并发编程中volatile实现过程详细解析相关推荐

  1. Java并发编程中的若干核心技术,向高手进阶

    来源:http://www.jianshu.com/p/5f499f8212e7 引言 本文试图从一个更高的视角来总结Java语言中的并发编程内容,希望阅读完本文之后,可以收获一些内容,至少应该知道在 ...

  2. 理解Java并发编程:volatile关键字解析

    文章目录 volatile关键字作用详解 原子/可见/有序 happen-before原则 volatile的作用 volatile的原理 volatile关键字作用详解 讲到Java中的volati ...

  3. synchronized 异常_由浅入深,Java 并发编程中的 Synchronized

    synchronized 作用 synchronized 关键字是 Java 并发编程中线程同步的常用手段之一. 1.1 作用: 确保线程互斥的访问同步代,锁自动释放,多个线程操作同个代码块或函数必须 ...

  4. Java并发编程:volatile关键字解析(转载)

    转自https://www.cnblogs.com/dolphin0520/p/3920373.html Java并发编程:volatile关键字解析 Java并发编程:volatile关键字解析 v ...

  5. 【Java并发编程:volatile关键字之解析】

    Java并发编程:volatile关键字解析 - Matrix海子 - 博客园 在Java 5之前,volatile是一个备受争议的关键字:因为在程序中使用它往往会导致出人意料的结果.在Java 5之 ...

  6. 由浅入深,逐步了解 Java 并发编程中的 Synchronized!

    作者 | sowhat1412  责编 | 张文 头图 | CSDN 下载自视觉中国 来源 | sowhat1412(ID:sowhat9094) synchronized 作用 synchroniz ...

  7. 转载:Java并发编程:volatile关键字解析

    看到一篇写的很细致的文章,感谢作者 作者:Matrix海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者Matrix海子和博客园共有,欢 ...

  8. JUC里面的相关分类|| java并发编程中,关于锁的实现方式有两种synchronized ,Lock || Lock——ReentrantLock||AQS(抽象队列同步器)

    JUC分类 java并发编程中,关于锁的实现方式有两种synchronized ,Lock AQS--AbstractQueuedSynchronizer

  9. java 线程由浅入深_由浅入深,Java 并发编程中的 Synchronized(一)

    synchronized 作用 synchronized 关键字是 Java 并发编程中线程同步的常用手段之一. 1.1 作用: 确保线程互斥的访问同步代,锁自动释放,多个线程操作同个代码块或函数必须 ...

最新文章

  1. 在linux和windows下自动备份数据库
  2. 什么是Java Marker Interface(标记接口)
  3. 阿里专家分享:企业级大数据轻量云实践
  4. 前台分页,感觉一般还能优化
  5. cookies和session区别
  6. 使用自定义线程池处理并行数据库流
  7. datagridview控件读写mysql数据库表格的方法_c# datagridview表格控件常用操作
  8. 计算机网络实验指导书 pdf,计算机网络实验指导书-20210608153043.pdf-原创力文档
  9. blender国内下载
  10. Http请求返回最外层的模型
  11. flink写入 mysql_基于 Binlog + Flink 实现多表数据同构/异构方案
  12. python3.7安装pyltp出错_安装pyltp遇到的问题及解决办法
  13. I.MX6 Android shutdown shell command
  14. 2019 vs 查看类图结构_在建筑网站上使用单页设计还是多页设计哪个更好_学云网...
  15. AHCI驱动下载与手动安装图解
  16. 四个vue后台常用模板,你用过几个?
  17. TO B的百度云新品问世/价格腰斩 智能化DNA能撑起百度野心?
  18. Android 自定义View(一)实现时钟表盘效果
  19. MIUI——添加学校邮箱到电子邮件解决方案
  20. 基于SSH框架的学生考试系统(注意看文档目录)

热门文章

  1. 智慧政务解决方案(28页)pdf_【金众电子】智慧政务解决方案
  2. Mybatis Integer类型参数值为0时判断为空、空字符串不通过
  3. word文本样式代码样式_使用文本样式表达创建真相来源
  4. 桌面图标摆放图案_用图标制作醒目的图案
  5. 云栖专辑 | 阿里开发者们的第6个感悟:享受折磨
  6. Nodejs 文件上传
  7. Linux文件和目录权限:chmod、更改所有者和所属组:chown,umask命令,隐藏权限:lsattr/chattr...
  8. 检查你的项目的引用包依赖关系
  9. ORA-16198: LGWR received timedout error from KSR
  10. Upgrade Hole puncher Mathematical Modeling