Java内存模型——volatile关键字

  最近工作中又用到了volatile关键字,一直以来就是单纯的使用,也没有仔细看过相关内容,这次借机会详细的整理了下有关volatile的资料,记录在案以备查阅。

  首先我们来看一个小例子:

 1 public class VolatileDemo1 {
 2     private boolean flag = true;
 3
 4     public static void main(String[] args) throws InterruptedException {
 5         VolatileDemo1 demo = new VolatileDemo1();
 6         Thread thread = new Thread(() -> {
 7             long start = System.currentTimeMillis();
 8             while (demo.flag) {
 9             }
10             long end = System.currentTimeMillis();
11             System.out.println("终止了while循环!flag的值为:" + demo.flag);
12             System.out.println("耗时:" + ( end -start ));
13         });
14         thread.start();
15         TimeUnit.SECONDS.sleep(2);
16         demo.flag = false;
17     }
18 }

  这段代码是volatile关键字的典型应用场景之一,两个线程(主线程与thread 线程)通过共享一个变量进行信息交互,在上一段代码中,由于没有为flag变量加上volatile关键字,可以预见,线程thread中的while循环并不会跳出。那么,是不是只有加volatile关键字可以解决这个问题呢?或者说我们能不能不改动代码就达到目的(主线程中改变flag的值后,thread线程可以读到,使while循环可以跳出)。答案当然是可以的,我们可以采用以下方式:(如图)

  在虚拟机参数选项上加上-Xint(请注意,这个参数在JDK1.8版本及以上),同样能够使while循环跳出,那么这个-Xint参数到底有什么作用呢?请看以下截图:

  这张截图来自Oracle的Java HotSpot VM Options 官方文档,翻译过来的意思是:“以纯解释模式运行应用程序。禁用编译到本机代码,所有字节码由解释器执行。在这种模式下,just in time (JIT)编译器所提供的性能优势并不存在。”这么说只要是禁止了JIT即时编译,就起到了和加volatile一样的作用,那他们两个有什么区别吗?JIT即时编译又做了什么呢?请继续往下看。

  要扯明白上面的问题,我们还要说下Java的内存模型,首先什么是内存模型呢?周所周知,在现代计算机硬件系统的不断改进中,CPU和内存之间的多级缓存机制导致的缓存一致性问题,以及为了高效执行代码而进行的处理器优化和指令重排序问题,是并发编程中的可见性、原子性、有序性问题的硬件层面原因。在并发编程中,为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。他解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。

  说的直白点,内存模型就是解决多线程场景下并发问题的一个重要规范,而不同的编程语言对于这个规范,在实现上可能有所不同,而Java内存模型(Java Memory Model ,JMM)就是Java编程语言提供的一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。同时Java中提供了一系列和并发处理相关的关键字,比如volatilesynchronized等,其实这些关键字就是对Java内存模型规范的一种实现,他们封装了Java内存模型规范底层的实现后提供给程序员使用,用来解决Java并发编程问题。

  有了上面的对于内存模型的描述,那么我们就很好理解了,如果要解决并发编程中的问题,最简单直接的做法就是不使用处理器执行代码的优化技术、不使用指令重排序、不使用CPU缓存等等优化技术。但是,这么做显然就是因噎废食了。而我们使用的-Xint参数,根据官网的的描述(没有JIT编译的部分,全部是由解释器解释执行)和最终的执行结果看,我们可以推论出:-Xint参数的作用在废止JIT即时编译应该也就是废除了指令重排序和CPU缓存等优化技术,这里我没有深入的研究过这个参数和JIT,只是临时应用,所以做出的推论完全是根据官网的的描述和代码的执行结果看,不一定完全正确,如果有了解的大神,还请不吝赐教。

  那下面我们就来看看volatile关键字,显然volatile关键字不会和-Xint一样因噎废食,全面封杀优化技术,那他是怎么做的呢?

  首先内存模型解决并发问题主要采用两种方式:限制处理器优化和使用内存屏障,volatile作为内存模型规范的一种应用实现方式,自然也是实现这两种方式。

  对于volatile变量,生成的汇编代码在volatile修饰的共享变量进行写操作的时候会多出一个Lock前缀的指令,将这个缓存中的变量回写到系统主存中。

  lock前缀指令实际上相当于一个内存屏障(也称内存栅栏),内存屏障会提供3个功能:

  1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

  2)它会强制将对缓存的修改操作立即写入主存;

  3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

  功能1相当于禁止指令重排序优化,解决了并发变成中有序性问题。

  功能2和3,由于他处理器的缓存遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个volatile变量在并发编程中,其值在多个缓存中是可见的,解决了并发变成中可见性问题。

  注意:volatile并不能解决原子性问题。

  通过以上一波操作,volatile完成了并发编程时解决有序性和可见性问题。

补充内容:

Java虚拟机有3种执行方式,分别是解释执行、混合模式和编译执行,默认情况下处于混合模式中

编译:字节码 --- jit提前编译 -- 汇编

解释:字节码 – 一段段编译 – 汇编

混合 :– 运行的过程中,JIT编译器生效,针对热点代码进行优化

内存屏障参考资料:

https://blog.csdn.net/dd864140130/article/details/56494925

参考资料:

http://www.uucode.net/201504/jvm5

https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html

原文地址:https://www.cnblogs.com/peripateticism/p/11065864.html

时间: 2024-10-06 13:02:53

Java内存模型——volatile关键字的相关文章

Java内存模型-volatile的内存语义

一 引言 听说在Java 5之前volatile关键字备受争议,所以本文也不讨论1.5版本之前的volatile.本文主要针对1.5后即JSR-133针对volatile做了强化后的了解. 二 volatile的特性 开门见山,volatile变量自身具有以下特性: 可见性(最重要的特性).对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入. 原子性.对任意(包括64位long类型和double类型)单个volatile变量的读/写具有原子性.但是类型于a

java内存模型-volatile

volatile 的特性 当我们声明共享变量为 volatile 后,对这个变量的读/写将会很特别.理解 volatile 特性的一个好方法是:把对 volatile 变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步.下面我们通过具体的示例来说明,请看下面的示例代码: class VolatileFeaturesExample { //使用volatile声明64位的long型变量 volatile long vl = 0L; public void set(long l) {

深入理解Java内存模型-volatile

volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这些单个读/写操作做了同步.下面我们通过具体的示例来说明,请看下面的示例代码: class VolatileFeaturesExample { volatile long vl = 0L; //使用volatile声明64位的long型变量 public void set(long l) { vl =

深入理解JVM读书笔记五: Java内存模型与Volatile关键字

12.2硬件的效率与一致性 由于计算机的存储设备与处理器的运算速度有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(Cache)来作为内存与处理器之间的缓冲:将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理器就无须等待缓慢的内存读写了. 基于高速缓存的存储交互很好地理解了处理器与内存的速度矛盾,但是也为计算机系统带来了更高的复杂度,因为它引入了一个新的问题: 缓存一致性(Cache Coherenc

由volatile关键字谈Java内存模型

volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatile关键字的实现原理,最后给出了几个使用volatile关键字的场景 1. 内存模型的相关概念 当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再

全面理解Java内存模型(JMM)及volatile关键字(转)

原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoader) 深入理解Java并发之synchronized实现原理 Java并发编程-无锁CAS与Unsafe类及其并发包Atomic 深入理解Java内存模型(JMM)及volatile关键字 剖析基于并发AQS的重入锁(Reetr

Java内存模型与volatile关键字浅析

volatile关键字在java并发编程中是经常被用到的,大多数朋友知道它的作用:被volatile修饰的共享变量对各个线程可见,volatile保证变量在各线程中的一致性,因而变量在运算中是线程安全的.但是经过深入研究发现,大致方向是对的 ,但是细节上不是这样. 首先,引出volatile的作用.情景:当线程A遇到某个条件时,希望线程B做某件事.像这样的场景应该是经常会遇到的吧,下面我们来看一段模拟代码: package com.jack.jvmstudy; public class Test

java 内存模型与volatile关键字

java内存模型可以大致理解分为两个模块,主内存和私有内存.主内存中主要是存放一些共享的全局变量,私有内存主要是存放线程所需的私有变量.一般情况下,如果某个线程需要使用主内存的全局变量.首先,它会拷贝一份主内存里面的全局变量到私有内存,进行操作,操作完成以后再把这个变量同步到主内存.如下图:如果是单线程的,到没什么问题,但是如果是多线程的,就有可能出现数据不一致的问题,因为线程之间是不可见的.看下面一个例子: package org.hzg.volatilekeyword; /** * Crea

深入java内存模型(二) volatile详解

对于volatile修饰符,我们应该不会陌生,在多线程中使用常见,比如多个线程想用一个全局变量作为标识符,或者一个共享变量,我们都会给该变量加上一个volatile的修饰符.volatile用中文解释是易变的,不稳定的.说明该变量会被多个线程访问并可能修改.那么jvm是怎样发挥volatile关键字的作用,如何实现的呢? 上一篇深入java内存模型中解释了jvm中的重排序以及四种内存屏障等.jvm总是会以一些易懂,使用方便的方式来实现相关功能.比如垃圾回收器,对于内存的申请与释放时一个令人头疼的