`
greemranqq
  • 浏览: 966447 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
社区版块
存档分类
最新评论

JAVA 内存模型(二) volatile理解

    博客分类:
  • JVM
阅读更多

    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 .这是一个顺序操作。但是多次执行可能会得出这样的结果:
A 线程 获得变量:0:true
这里是A线程产生的,当A线程执行while(!flag) 为真,也就是flag = false.然后进入方法体,这时候B同时执行,将flag 变成了true,奇怪的是number 变量居然还是0.也就是说flag = true (可能)被提前执行了!!
但是我们的要求是flag = true 必须后于 number = 100 执行。JMM(JAVA内存模型) 为先限制这种指令重排序问题,加 入了volatile ,对于这两个操作的指令重排序问题,我仅仅找了一下规则,具体的还得根据JMM里面 的 8个基本操作入手,这里我转了一部分规则:
是否重排序               第二个操作
第一个操作 普通读/写 volatile读 volatile写
普通读/写     NO
volatile读 NO NO NO
volatile写   NO NO

我们以A,B操作,V,T变量,A:普通变量V 读/写  和 B:volatile变量T  写进行解释(第三行,最后一个格子)
如果A是对普通变量V进行读/写 操作。B对T 进行写操作,编译器不做重排序。
从表关系看出,我们的 number = 100,flag = true ,都有写操作。要想不重排序,我们可以在第二个变量flag 上加上volatile 就行了,但是给number 加上volatile 就不行,就是最后一行,第一个空格了,没效果。
 
小结:
        1.这里仅仅对volatile 进行概念性的理解
        2.关于重排序问题,涉及到内存和硬件、系统等差异,这里仅仅对JMM 这层进行介绍,关于volatile 对重排序的的原理,在上面贴出的链接有介绍。
        3.了解volatile 关键字,不至于我们乱用它,要想使用灵活准确,需要更加深入实践。
        4.个人理解对volatile 更多使用应该专注于它的可见性,多线程的情况下只允许一个线程有逻辑修改,而其他线程用于读取的一致性。当然在指令重排问题,在保证上一点的情况下,可以避免。
        
 
 
 
 
 
 
更多请参考:

 

  • 大小: 10.7 KB
分享到:
评论

相关推荐

    深入理解 Java 内存模型.pdf

    java线程之间的通信对程序员完全透明,内存可见性问题很容易困扰java程序员,本文试图揭开java内存模型神秘的面纱。本文大致分三部分:重排序与顺序一致性;三个同步原语(lock,volatile,final)的内存语义,重...

    深入理解Java内存模型.程晓明(带书签文字版).pdf

    Java 内存模型的抽象 4 重排序 6 处理器重排序与内存屏障指令 7 happens-before 10 重排序 13 数据依赖性 13 as-if-serial 语义 13 程序顺序规则 15 重排序对多线程的影响 15 顺序一致性 19 数据竞争与顺序...

    深入理解java内存模型

    Java内存模型的抽象 重排序 处理器重排序与内存屏障指令 happens-before 重排序 数据依赖性 as-if-serial 语义 程序顺序规则 重排序对多线程的影响 顺序一致性 数据竞争与顺序一致性保证 顺序一致性内存模型 同步...

    深入理解 Java 内存模型 - v1.0

    详细介绍Java内存,ava线程之间的通信对程序员完全透明,内存可见性问题很容易困扰java程序员,本文试图揭开java内存模型神秘的面纱。本文大致分三部分:重排序与顺序一致性;三个同步原语(lock,volatile,final)...

    深入理解Java内存模型

    Agenda: •什么是Java内存模型JMM •内存可见性 •有序性 •指令重排序 •内存屏障 •顺序一致性与Happens-before规则 •volatile, synchronized, 原子变量,锁, final的原理

    深入理解JAVA内存模型(高清完整版)

    对JMM大致的介绍了一下,尤其是同步原语synchronized,volatile,final有一个很详细的介绍,可以学习学习

    Java面试-讲一讲什么是Java内存模型

    讲一讲什么是Java内存模型 Java内存模型虽说是一个老生常谈的问题 ,也是大厂面试中绕不过的,甚至初级面试也会问到。但是真正要理解起来,还是相当困难,主要这个东西看不见,摸不着。 这是一个比较开放的题目,...

    深入理解Java内存模型??volatile

    理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这些单个读/写操作做了同步。下面我们通过具体的示例来说明,请看下面的示例代码: class VolatileFeaturesExample { ...

    深入理解Java内存模型??final

    与前面介绍的锁和volatile相比较,对final域的读和写更像是普通的变量访问。对于final域,编译器和处理器要遵守两个重排序规则:  1、在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个...

    深入理解_Java_虚拟机 JVM_高级特性与最佳实践

    / 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和...

    Java并发编程实战 02Java如何解决可见性和有序性问题

    摘要 在上一篇文章当中,讲到了CPU缓存...Java内存模型是有一个很复杂的规范,但是站在程序员的角度上可以理解为:Java内存模型规范了JVM如何提供按需禁用缓存和编译优化的方法。 具体包括 volatile、synchronized、fi

    深入理解Java源码:提升技术功底,深度掌握技术框架,快速定位线上问题

    Netty的高并发高性能架构设计精髓包括:主从Reactor线程模型,NIO多路复用非阻塞,无锁串行化设计思想,支持高性能序列化协议,零拷贝(直接内存的使用),ByteBuf内存池设计,灵活的TCP参数配置能力,以及并发优化...

    深入理解JVM内存结构及运行原理全套视频加资料.txt

     第28讲 Java内存区域-直接内存和运行时常量池 00:15:53  第29讲 对象在内存中的布局-对象的创建 00:21:19  第30讲 探究对象的结构 00:13:47  第31讲 深入理解对象的访问定位 00:08:01  第32讲 垃圾回收-...

    Java虚拟机

    原子性、可见性和有序性在Java内存模型中的体现;先行发生原则的规则和使用;线程在Java语言中的实现原理;虚拟机实现高效并发所做的一系列锁优化措施。 前言 第一部分 走近Java 第1章 走近Java 1.1 概述 1.2 ...

    【2018最新最详细】并发多线程教程

    3.java内存模型以及happens-before规则 4.彻底理解synchronized 5.彻底理解volatile 6.你以为你真的了解final吗? 7.三大性质总结:原子性、可见性以及有序性 8.初识Lock与AbstractQueuedSynchronizer(AQS) 9.深入...

    Java并发编程原理与实战

    通过生产者消费者模型理解等待唤醒机制.mp4 Condition的使用及原理解析.mp4 使用Condition重写waitnotify案例并实现一个有界队列.mp4 深入解析Condition源码.mp4 实战:简易数据连接池.mp4 线程之间通信之join应用与...

    涵盖了90%以上的面试题

    Java的内存模型(JVM的内存划分) JVM内存模型1.7和1.8的区别 如何判断一个对象是否是垃圾对象 垃圾回收算法 Minor GC和Full GC 垃圾收集器 集合的继承体系 Collection 和 Collections的区别。 如何通过jdbc访问...

    龙果java并发编程完整视频

    第30节通过生产者消费者模型理解等待唤醒机制00:20:50分钟 | 第31节Condition的使用及原理解析00:17:40分钟 | 第32节使用Condition重写wait/notify案例并实现一个有界队列00:22:05分钟 | 第33节深入解析Condition...

Global site tag (gtag.js) - Google Analytics