`
zhhphappy
  • 浏览: 121417 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Java 内存可见性与volatile

    博客分类:
  • java
 
阅读更多

在多核系统中,处理器一般有一层或者多层的缓存,这些的缓存通过加速数据访问(因为数据距离处理器更近)和降低共享内存在总线上的通讯(因为本地缓存能够满足许多内存操作)来提高CPU性能。如图:处理器的多层缓存模型



 JVM需要实现跨平台的支持,它需要有一套自己的同步协议来屏蔽掉各种底层硬件和操作系统的不同,因此就引入了Java内存模型JMM

JMMJava Memory Model)主要是为了规定了线程和内存之间的一些关系。系统存在一个主内存(Main Memory)Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。如图:Java内存模型



在java内存模型中,共享变量会在多线程中存在可见性的问题。如下面代码中的例子:
private static boolean ready
…..
Thread1,2,3…
while (!ready) {
  // do something unready…
}
// do something ready
……
Thread x….
ready = true;
 当Thread x中设置ready = true时,会将该值写入工作内存,并同步到主内存,但其他线程有可能还是读取到自己工作内存中缓存的老数据,从而导致其他线程可能看不到ready=true而不跳出循环,以上就是一个典型的java内存可见性问题。
 
当然java内存模型也定义了一系列解决可见性(工作内存和主内存交互协议)方法,包括volatile,synchronized,锁等方式,这里主要用volatile来说明可见性问题。
先看volatile的定义:
 java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。

调整前面示例代码中共享变量为volatile并写出完整的测试代码:

public class VisibilityDemo {

    private static volatile boolean ready;

    static class Reader extends Thread {
        @Override
        public void run() {
            long tryTimes = 0L;
            while (!ready) {
                ++tryTimes;
            }
            System.out.println("ready! try times : " + tryTimes);
        }
    }

    static class Writer extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100000; i++) {
                ready = true;
            }
        }
    }

    public static void main(String[] args) throws Exception{
        new Reader().start();

        Thread.sleep(100L);

        new Writer().start();
    }
}

 加了volatile后Reader线程能成功退出并打印出tryTimes。

用javap –c –l –s –verbose VisibilityDemo 查看增加volatile前后的字节码没有区别,直接看JIT运行时汇编码:

环境:

~$ file /sbin/init
/sbin/init: ELF 64-bit LSB  shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=7d9cc5d4d6cb68aede9400492a7c5942c55c7598, stripped
~$ java -version
java version "1.7.0_55"
Java(TM) SE Runtime Environment (build 1.7.0_55-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)

 打印JIT汇编码:

Java -client -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly VisibilityDemo > ./ VisibilityDemo.assemblycode

 

0x00007f02c8c3b2d6: mov    %sil,0x70(%r10)
0x00007f02c8c3b2da: lock addl $0x0,(%rsp)     ;*putstatic ready

 

 有volatile变量修饰的共享变量进行写操作的时候会多第二行汇编代码,通过查IA-32架构软件开发者手册可知,lock前缀的指令在多核处理器下会引发了两件事情:
将当前处理器缓存行的数据会写回到系统内存。
这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。
 
处理器为了提高处理速度,不直接和内存进行通讯,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完之后不知道何时会写到内存,如果对声明了Volatile变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。
 
Lock前缀指令会引起处理器缓存回写到内存。Lock前缀指令导致在执行指令期间,声言处理器的 LOCK# 信号。在多处理器环境中,LOCK# 信号确保在声言该信号期间,处理器可以独占使用任何共享内存。(因为它会锁住总线,导致其他CPU不能访问总线,不能访问总线就意味着不能访问系统内存),但是在最近的处理器里,LOCK#信号一般不锁总线,而是锁缓存,毕竟锁总线开销比较大。但在P6和最近的处理器中,如果访问的内存区域已经缓存在处理器内部,则不会声言LOCK#信号。相反地,它会锁定这块内存区域的缓存并回写到内存,并使用缓存一致性机制来确保修改的原子性,此操作被称为“缓存锁定”,缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据。

 

 

参考文章:http://ifeve.com/volatile/

分析生成的汇编代码 http://blog.csdn.net/hengyunabc/article/details/26898657

  • 大小: 19.4 KB
  • 大小: 21 KB
分享到:
评论

相关推荐

    Java volatile与AQS锁内存可见性

    从JUC中的AQS引入,讲解Java volatile与AQS锁内存可见性

    Java并发:volatile内存可见性和指令重排

     1、保证内存可见性  2、防止指令重排  此外需注意volatile并不保证操作的原子性。  (一)内存可见性  1 概念  JVM内存模型:主内存和线程独立的工作内存  Java内存模型规定,对于多个线程共享的变量...

    Java 内存模型

    Java内存模型的核心内容涵盖了锁、线程间的交互、内存可见性和顺序一致性等方面。在JSR-133之前的Java内存模型规范中,volatile变量的语义较弱,它们的访问可以自由排序。但在新规范中,volatile变量的语义被加强为...

    深入理解Java内存模型

    由于本地内存的存在,JMM需要控制主内存与每个线程的本地内存之间的交互,以确保内存可见性。 Java内存模型中的重排序是一个重要组成部分,它分为编译器重排序、指令级并行的重排序以及内存系统的重排序。编译器...

    Java并行(3):可见性重访之锁、Volatile与原子变量1

    在Java并发编程中,内存可见性和线程安全是核心议题。本文将探讨三个关键概念:过期数据、锁的可见性以及Volatile关键字的作用。首先,我们来看一下“过期数据”这一问题。 1. 过期数据 在并发环境中,当多个线程...

    Java并发volatile可见性的验证实现

    Java并发volatile可见性的验证实现 在Java并发编程中,volatile关键字扮演着非常重要的角色,它可以确保变量的可见性和原子性。在多线程环境下,volatile关键字可以确保变量的修改对其他线程是可见的。本文将通过...

    深入理解Java内存模型 pdf 超清版

    - 描述了两个操作之间的顺序关系,是JMM保证内存可见性的基础。 - 例如,一个线程初始化一个对象,然后另一个线程访问这个对象,Happens-Before原则确保了初始化操作对其他线程可见。 4. **重排序** - 编译器和...

    深入理解 Java 内存模型

    Java 内存模型(Java Memory Model,简称 JMM)是 Java 平台中关于线程如何访问共享变量的一套规则,它定义了线程之间的内存可见性、数据一致性以及指令重排序等关键概念,对于多线程编程和并发性能优化至关重要。...

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

    2. **单例模式中的双重检查锁定**:在实现线程安全的单例模式时,`volatile`关键字可以用来保证实例创建过程中的可见性和有序性,避免多线程环境下可能出现的问题。 3. **懒汉式初始化**:在某些情况下,为了避免...

    java里的volatile关键字详解

    Java中的Volatile关键字详解是Java中的一种关键字,用于保证线程之间的可见性、原子性和有序性。下面是对Java中的Volatile关键字详解的知识点总结: 一、基本概念 1. 可见性:可见性是一种复杂的属性,因为可见性...

    Java理论与实践:修复Java内存模型1

    Java内存模型(Java Memory Model, JMM)是Java平台中用于规范线程间通信和内存可见性的重要概念,它的目标是确保多线程环境下的正确同步。然而,原始的JMM存在一些严重的缺陷,导致了开发者在理解和实现线程安全时...

    18Java内存模型:Java中的volatile有什么用?1

    1. **可见性**:当一个线程修改了一个`volatile`变量时,这个修改会立即对其他所有线程可见。这意味着一旦一个线程修改了`volatile`变量,其他线程不会从自己的本地缓存中读取旧值,而是会立即获取到最新值。这对于...

    java内存模型文档

    - 这是判断数据是否存在竞争、是否需要同步的一个依据,规定了内存可见性的顺序。 7. **原子操作与CAS** - 原子操作(如AtomicInteger)在不使用锁的情况下保证了更新操作的原子性。 - CAS(Compare and Swap)...

    深入理解 Java 内存模型_程晓明_InfoQ_java_内存模型_

    例如,`volatile`关键字可以提供这种可见性保证。 2. **原子性**:一个操作要么完全完成,要么不进行。`synchronized`关键字可以确保对变量的修改是原子性的。 3. **有序性**:限制编译器和处理器为了优化而进行的...

    Java内存模型--原子性;有序性;可见性1

    总结来说,Java内存模型通过原子性、有序性和可见性保证了多线程环境下的数据一致性。理解并熟练运用这些概念是编写高效、线程安全的Java代码的基础。在实际开发中,应根据需求选择合适的方式来确保这三个特性,以...

    深度剖析java内存模型

    JMM通过控制主内存与每个线程的本地内存之间的交互,为Java程序员提供了内存可见性的保证。 JVM对Java内存模型的实现将内存分为两部分:线程栈区和堆区。每个线程拥有自己的线程栈,其中包含了线程执行的方法调用...

    Java内存模型分析与其在编程中的应用.pdf

    Java内存模型通过提供synchronized关键字和volatile变量来实现线程间通信和内存可见性。 synchronized关键字可以用来修饰方法或代码块,确保同一时刻只有一个线程能够执行被同步的代码块,从而防止多个线程同时访问...

    Java并发编程系列- volatile

    总的来说,`volatile`关键字在Java并发编程中起到的作用是确保共享变量的即时可见性和防止指令重排,但并不保证操作的原子性。理解并合理使用`volatile`可以有效地提升多线程程序的并发性能,但同时也需要注意它的...

Global site tag (gtag.js) - Google Analytics