变量不可见的两个原因

Java每个线程工作都有一个工作空间,需要的变量都是从主存中加载进来的。Java内存模型如下(JMM):

线程访问一个共享的变量时,都需要先从主存中加载一个副本到自己的工作内存中,经过自己修改后再更新到主存中去。在这个过程中可能出现这种情况:线程A在工作内存中修改了变量1的值,但是还没有写入主存,这档口线程B将变量1加载到自己工作内存中。显然,线程B拿到的不是变量1的最新值了。

变量可见性就是: 这个变量被任何一个线程修改了,其他线程都能“看见”,也就是能取到变量最新的值。

重排序是指为了适合cpu指令执行机制,编译器、内存系统、处理器可能会对一些指令的执行顺序进行重排。例如:

int a = 1;               //line1
int b = 2; //line2
int s = a*b; //line3

line1 和 line2 可能会被重排颠倒位置,但是line3不会重排,因为Java单线程下会遵守 as-if-serial语义,简单的讲就是重排指令不会出现错误的结果。在多线程下,指令重排则可能造成一些问题,例如:

class Example {
int a = 0;
boolean flag = false; public void writer() {
a = 1;
flag = true;
} public void reader() {
if (flag) {
int i = a +1;
}
}
}

线程A首先执行writer()方法,线程B线程接着执行reader()方法。线程B在int i=a+1 时不一定能看到a已经被赋值为1,因为在writer()中,两句话顺序可能打乱:

线程A执行顺序:  flag=true;(a=1;还没执行完或还没写到主存)

线程B执行:flag=true  (而此时a=0)    产生了一些与我们预期之外的情况。

导致变量不可见的原因(1)更新不及时,(2)多线程交替执行时的指令重排序。

volatile实现可见性的原理

JVM线程工作时的原子性指令有:

    read: 从主存读取一个变量的值的副本到线程的工作内存。

    load:read读来的值,把这个值填充到线程使用的变量中,然后就可以使用了。

    use:要使用一个变量,先发出这个指令。

    assign:赋值,给变量一个新值。

    write:将变量的新值运送到主存中。

    store:将写到主存的新值存到那个变量中。

 上述操作必定是顺序执行的,但可不一定连续,中间可能插入其他指令。为了保证可见性:关键就是保证load、use的执行顺序不被打乱(保证使用变量前一定进行了load操作,从主存拿最新值来),assign、wirte的执行顺序不被打乱(保证赋值后马上就是把值写到主存)。

所以使用内存屏障, CPU指令,可以禁止指令执行乱序:插入一个内存屏障, 相当于告诉CPU和编译器指令顺序先于这个指令的必须先执行,后于这个命令的必须后执行。

解决第一个导致不可见的因素(更新不及时):内存屏障,对于volatile修饰的变量,读操作时在读指令use插入一条读屏障指令重新从主存加载最新值进来,保证了load、use指令的执行顺序不乱;写操作时在写指令assign插入一条写屏障指令,将工作内存变量的最新值立刻写入主存变量。

 解决第二个因素(指令重排): 由于读写数据时会在之前/后插入一条内存屏障指令,因此volatile可以禁止指令重排序。

Synchronized实现可见性原理

解决第一个因素:在加锁前会将工作内存的值全部重新加载一遍,保证最新;释放锁前将工作内存的值全部更新到主存;由于在带锁期间,没有其他线程能访问本线程正在使用的共享变量,这样就保证了可见性。

解决第二个因素: 由于Synchronized修饰的代码块都是原子性执行的,即一旦开始做,就会一直执行完毕,期间有其他线程不可以访问本线程所使用的共享变量,这样,即便指令重排了也不会出现问题。

volatile不具有原子性可能导致的问题

经过前面的总结,可以看出Synchronized包裹的代码里的共享变量有可见性、指令不可排序性、原子性;volatile修饰的变量有可见性、指令不可排序性;volatile并不保证变量有原子性。如果用volatile修饰变量希望保证它的原子性就可能出现问题,例如:

volatile  int num = 0;

线程A:
num++; 线程B:
num++;

两个线程操作都进行num++的操作,理论上完事后 num 的值为2,但是num++ 的操作本身不是原子性的(包括读取 num原先的值、+1、把+1后的值写入num),volatile也不能使对num的操作变为原子性。因此可能有num = 1的结果:

线程A:读取了num的值,为0,然后阻塞了。

线程B:读取num的值,还是为0,+1后立即写入主存,num = 1(体现可见性)。

线程A:恢复执行了,已经做完读取操作,工作内存中num = 0,继续执行。num+1,写入内存,num = 1;

虽然这种情况发生的概率很小,但是并发量大时还是会出现不少这种情况的。(可以用Synchronized、lock、AtomicInteger等解决)

由于原子性的问题,volatile不适合修饰依赖自身的变量 :  num++、a = a*2...    也不适合修饰不变式的变量(即volatile变量应该是独立的):a<b....

volatile、Synchronized实现变量可见性的原理,volatile使用注意事项的更多相关文章

  1. 使用 volatile 关键字保证变量可见性和禁止指令重排序

    volatile 概述 volatile 是 Java 提供的一种轻量级的同步机制.相比于传统的 synchronize,虽然 volatile 能实现的同步性要差一些,但开销更低,因为它不会引起频繁 ...

  2. volatile关键字与内存可见性

    前言 首先,我们使用多线程的目的在于提高程序的效率,但是如果使用不当,不仅不能提高效率,反而会使程序的性能更低,因为多线程涉及到线程之间的调度.CPU上下文的切换以及包括线程的创建.销毁和同步等等,开 ...

  3. java 轻量级同步volatile关键字简介与可见性有序性与synchronized区别 多线程中篇(十二)

    概念 JMM规范解决了线程安全的问题,主要三个方面:原子性.可见性.有序性,借助于synchronized关键字体现,可以有效地保障线程安全(前提是你正确运用) 之前说过,这三个特性并不一定需要全部同 ...

  4. volatile和synchronized实现内存可见性的区别

    先看看synchronized实现内存可见性 加锁(synchronized同步)的功能不仅仅局限于互斥行为,同时还存在另外一个重要的方面:内存可见性.我们不仅希望防止某个线程正在使用对象状态而另一个 ...

  5. volatile关键字与内存可见性&原子变量与CAS算法

    1 .volatile 关键字:当多个线程进行操作共享数据时, 可以保证内存中的数据可见 2 .原子变量:jdk1.5后java.util.concurrent.atomic 包下提供常用的原子变量 ...

  6. jvm高级特性(5)(1)(原子性,可见性,有序性,volatile,概述)

    JVM高级特性与实践(十二):高效并发时的内外存交互.三大特征(原子.可见.有序性) 与 volatile型变量特殊规则 简介: 阿姆达尔定律(Amdahl):该定律通过系统中并行化与串行化的比重来描 ...

  7. JMM内存模型+volatile+synchronized+lock

    硬件内存模型: Java内存模型: 每个线程都有一个工作内存,线程只可以修改自己工作内存中的数据,然后再同步回主内存,主内存由多个内存共享. 下面 8 个操作都是原子的,不可再分的: 1)  lock ...

  8. 【JUC系列第一篇】-Volatile关键字及内存可见性

    作者:毕来生 微信:878799579 什么是JUC? JUC全称 java.util.concurrent 是在并发编程中很常用的实用工具类 2.Volatile关键字 1.如果一个变量被volat ...

  9. Volatile如何保证线程可见性之总线锁、缓存一致性协议

    基础知识回顾 下图给出了假想机的基本设计.中央处理单元(CPU)是进行算术和逻辑操作的部件,包含了有限数量的存储位置--寄存器(register),一个高频时钟.一个控制单元和一个算术逻辑单元. 时钟 ...

随机推荐

  1. Skier 游戏

    # Listing_10-1.py # Copyright Warren Sande, 2009 # Released under MIT license http://www.opensource. ...

  2. 【iOS】Error: Error Domain=PBErrorDomain Code=7 "Cannot connect to pasteboard server

    这几天在用 Swift 开发一个简单的键盘扩展,真机调试时遇到了这个问题,详细信息如下: ***[:] Could not save pasteboard named com.apple.UIKit. ...

  3. JWT token 跨域认证

    JSON Web Token(缩写 JWT),是目前最流行的跨域认证解决方案. session登录认证方案:用户从客户端传递用户名.密码等信息,服务端认证后将信息存储在session中,将sessio ...

  4. Another option to bootup evidence files

    When it comes to booting up evidence files acquired from target disk, you got two options. One is VF ...

  5. Something wrong with EnCase v8 index search results

    My friend told me that she installed EnCase v8.05 on her workstation which OS version is Win 10. She ...

  6. wamp不显示文件图标

    wamp不显示文件图标 效果如下图 右键图片"在新的标签页打开图片"后会跳转到404页面,并显示The requested URL /icons/unknown.gif was n ...

  7. NS3中一些难以理解的常数

    摘要:在NS3的学习中,PHY MAC中总有一些常数,需要理解才能修改.如帧间间隔等.那么,本文做个简单分析,帮助大家理解.针对802.11标准中MAC协议.   void WifiMac::Conf ...

  8. Linux系统与程序监控工具atop教程

    引言 Linux以其稳定性,越来越多地被用作服务器的操作系统(当然,有人会较真地说一句:Linux只是操作系统内核:).但使用了Linux作为底层的操作系统,是否我们就能保证我们的服务做到7*24地稳 ...

  9. 【0808 | Day 11】文件的高级应用/修改以及函数的定义/使用/参数

    文件的高级应用 一.三种模式 'r+'模式 with open('test.py','r',encoding = 'utf8') as fr: print(fr.writable()) fr.writ ...

  10. .Net Core2.1 秒杀项目一步步实现CI/CD(Centos7.2)系列一:k8s高可用集群搭建总结以及部署API到k8s

    前言:本系列博客又更新了,是博主研究很长时间,亲自动手实践过后的心得,k8s集群是购买了5台阿里云服务器部署的,这个集群差不多搞了一周时间,关于k8s的知识点,我也是刚入门,这方面的知识建议参考博客园 ...