volatile 关键字是JAVA虚拟机提供的最轻量级的同步机制,在了解volatile的特性后,会让我们在多线程数据竞争方面更准确的使用。
一、概念解释
一个volatile 变量,具备两种特性,第一是保证此变量多所有线程的可见性,这里的“可见性”是指当一个线程修改了该变量之后,新值对其他线程来说是可以立即得知的,而普通变量做不到,普通变量需要传递和主内存来完成,比如:线程A修改了普通变量的值,然后向主内存进行回写,另一条线程B在A线程回写完成之后再从主内存进行读取操作,变量的新值才会对线程B可见。(参考上一篇内存模型图)
volatitle 变量的可见性描述是:“volatile变量对所有线程是立即可见的,对volatile 变量的所有写操作都能理解反应到其他线程中,也就是说volatile 变量再各个线程中是一致的。”这句话是对的,但是常常有点人就会通过上面描述得出:“基于volatile 变量在并发下是安全的。”这样的结论。volatile 变量再各个线程的工作内存中不存在一致性问题(因为volatile 变量再使用前,都会先刷新,即使刷新前不一致,执行引擎都是看到刷新后的值,因此可认为是一致的)。而Java 里面的运算并非原子性操作,导致volatile 变量的运算在并发下一样是不安全的,看代码:
package com; /** * volatile 测试 * @author Ran */ public class VolatileTest { public static volatile int race = 0; public static void increase(){ race ++ ; } // 线程数 public static final int THREADS_COUNT = 20; public static void main(String[] args) { Thread[] threads = new Thread[THREADS_COUNT]; for(int i = 0;i < THREADS_COUNT;i++){ threads[i] = new Thread(new Runnable() { @Override public void run() { for(int i = 0;i<10000;i++){ increase(); } } }); threads[i].start(); } // 等待所有累加线程结束 while(Thread.activeCount() > 1){ Thread.yield(); } System.out.println(race); } }
以上代码理论结果应该是200000,但是你会发现值始终是小于这个数的,这是因为race 的自增运算不是原子性的,我们看反编译的字节码:
从字节码看出,假设A线程执行getstatic 指令把race 的值取到操作栈顶时,volatile 保证了race 的的值此时是正确的假设现在是10,然后执行iconst_1 和iadd 的时候,可能另外B线程已经完成了上面的所有操作,值已经变成11了,这个时候A线程拿到的10 已经是过期数据,这时候A继续完成下面的操作的时候,即使增加了还是11(理论两个线程完成,会变成12)。在多线程情况下,累加的值也就小于预期了。
这里从上篇的内存模型来理解:
A,B 线程,分别从主内存拿到(read) volatile 变量race=0.然后放到(load)A,B的工作内存,这时候A线程把变量传递(use)给执行引擎,按字节码进行操作。同时B 执行同样的动作,由于JVM的不确定性,A在执行到iconst_1 和iadd 的时候,B已经执行完成,这时候A继续执行,最后刷新主内存的只1,结果就不是预期的了。
上述简单的解释:A,B 线程获取volatille 变量,每次都要从新从主内存读取,并且A线程改变了变量值,会告诉B线程告诉B线程,我已经改变了,你读取必须从主内存读取。但是在A线程改变,写入主内存和发送通知时,B线程获得的变量已经是主内存中读取的了,不需要从新读取,那么此时错误就产生了。
二、解决指令从排序
这里先看一段有趣的代码:
// 变量 private static boolean flag = false;; private static int number; // 模拟初始化数字number private static class B extends Thread { @Override public void run() { number = 100; flag = true; } } public static void main(String[] args) { new A().start(); new B().start(); } // 模拟获得B 初始化后的值 private static class A extends Thread { @Override public void run() { while(!flag){ System.out.println("A 线程 获得变量:"+number +":"+flag); Thread.yield(); } System.out.println("B 线程执行完成:"+number+":"+flag); } }
上面操作是模拟多线程A线程要检测和获得B线程初始化Number 的值,也就是说B线程中当number = 100,flag = true .这是一个顺序操作。但是多次执行可能会得出这样的结果:
相关推荐
java线程之间的通信对程序员完全透明,内存可见性问题很容易困扰java程序员,本文试图揭开java内存模型神秘的面纱。本文大致分三部分:重排序与顺序一致性;三个同步原语(lock,volatile,final)的内存语义,重...
Java 内存模型的抽象 4 重排序 6 处理器重排序与内存屏障指令 7 happens-before 10 重排序 13 数据依赖性 13 as-if-serial 语义 13 程序顺序规则 15 重排序对多线程的影响 15 顺序一致性 19 数据竞争与顺序...
Java内存模型的抽象 重排序 处理器重排序与内存屏障指令 happens-before 重排序 数据依赖性 as-if-serial 语义 程序顺序规则 重排序对多线程的影响 顺序一致性 数据竞争与顺序一致性保证 顺序一致性内存模型 同步...
详细介绍Java内存,ava线程之间的通信对程序员完全透明,内存可见性问题很容易困扰java程序员,本文试图揭开java内存模型神秘的面纱。本文大致分三部分:重排序与顺序一致性;三个同步原语(lock,volatile,final)...
Agenda: •什么是Java内存模型JMM •内存可见性 •有序性 •指令重排序 •内存屏障 •顺序一致性与Happens-before规则 •volatile, synchronized, 原子变量,锁, final的原理
对JMM大致的介绍了一下,尤其是同步原语synchronized,volatile,final有一个很详细的介绍,可以学习学习
讲一讲什么是Java内存模型 Java内存模型虽说是一个老生常谈的问题 ,也是大厂面试中绕不过的,甚至初级面试也会问到。但是真正要理解起来,还是相当困难,主要这个东西看不见,摸不着。 这是一个比较开放的题目,...
理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这些单个读/写操作做了同步。下面我们通过具体的示例来说明,请看下面的示例代码: class VolatileFeaturesExample { ...
与前面介绍的锁和volatile相比较,对final域的读和写更像是普通的变量访问。对于final域,编译器和处理器要遵守两个重排序规则: 1、在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个...
/ 316 12.1 概述 / 316 12.2 硬件的效率与一致性 / 317 12.3 Java内存模型 / 318 12.3.1 主内存与工作内存 / 319 12.3.2 内存间交互操作 / 320 12.3.3 对于volatile型变量的特殊规则 / 322 12.3.4 对于long和...
摘要 在上一篇文章当中,讲到了CPU缓存...Java内存模型是有一个很复杂的规范,但是站在程序员的角度上可以理解为:Java内存模型规范了JVM如何提供按需禁用缓存和编译优化的方法。 具体包括 volatile、synchronized、fi
Netty的高并发高性能架构设计精髓包括:主从Reactor线程模型,NIO多路复用非阻塞,无锁串行化设计思想,支持高性能序列化协议,零拷贝(直接内存的使用),ByteBuf内存池设计,灵活的TCP参数配置能力,以及并发优化...
第28讲 Java内存区域-直接内存和运行时常量池 00:15:53 第29讲 对象在内存中的布局-对象的创建 00:21:19 第30讲 探究对象的结构 00:13:47 第31讲 深入理解对象的访问定位 00:08:01 第32讲 垃圾回收-...
原子性、可见性和有序性在Java内存模型中的体现;先行发生原则的规则和使用;线程在Java语言中的实现原理;虚拟机实现高效并发所做的一系列锁优化措施。 前言 第一部分 走近Java 第1章 走近Java 1.1 概述 1.2 ...
3.java内存模型以及happens-before规则 4.彻底理解synchronized 5.彻底理解volatile 6.你以为你真的了解final吗? 7.三大性质总结:原子性、可见性以及有序性 8.初识Lock与AbstractQueuedSynchronizer(AQS) 9.深入...
通过生产者消费者模型理解等待唤醒机制.mp4 Condition的使用及原理解析.mp4 使用Condition重写waitnotify案例并实现一个有界队列.mp4 深入解析Condition源码.mp4 实战:简易数据连接池.mp4 线程之间通信之join应用与...
Java的内存模型(JVM的内存划分) JVM内存模型1.7和1.8的区别 如何判断一个对象是否是垃圾对象 垃圾回收算法 Minor GC和Full GC 垃圾收集器 集合的继承体系 Collection 和 Collections的区别。 如何通过jdbc访问...
第30节通过生产者消费者模型理解等待唤醒机制00:20:50分钟 | 第31节Condition的使用及原理解析00:17:40分钟 | 第32节使用Condition重写wait/notify案例并实现一个有界队列00:22:05分钟 | 第33节深入解析Condition...