Java内存模型与volatile关键字

转载 2015年07月10日 22:51:19

Java内存模型(Java Memory Model)


Java内存模型(JMM),不同于Java运行时数据区,JMM的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中读取数据这样的底层细节。JMM规定了所有的变量都存储在主内存中,但每个线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量,工作内存是线程之间独立的,线程之间变量值的传递均需要通过主内存来完成。

volatile关键字


平时在阅读jdk源码的时候,经常看到源码中有写变量被volatile关键字修饰,但是却不是十分清除这个关键字到底有什么用处,现在终于弄清楚了,那么我就来讲讲这个volatile到底有什么用吧。

当一个变量被定义为volatile之后,就可以保证此变量对所有线程的可见性,即当一个线程修改了此变量的值的时候,变量新的值对于其他线程来说是可以立即得知的。可以理解成:对volatile变量所有的写操作都能立刻被其他线程得知。但是这并不代表基于volatile变量的运算在并发下是安全的,因为volatile只能保证内存可见性,却没有保证对变量操作的原子性。比如下面的代码:

/**
 * 发起20个线程,每个线程对race变量进行10000次自增操作,如果代码能够正确并发,
 * 则最终race的结果应为200000,但实际的运行结果却小于200000。
 * 
 * @author Colin Wang
 *
 */
public class VolatileTest {
    public static volatile int race = 0;

    public static void increase() {
        race++;
    }

    private 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);
    }
}

这便是因为race++操作不是一个原子操作,导致一些线程对变量race的修改丢失。若要使用volatale变量,一般要符合以下两种场景:

  1. 变量的运算结果并不依赖于变量的当前值,或能够保证只有单一的线程修改变量的值。
  2. 变量不需要与其他的状态变量共同参与不变约束。

使用volatile变量还可以禁止JIT编译器进行指令重排序优化,这里使用单例模式来举个例子:

/**
 * 单例模式例程一
 * 
 * @author Colin Wang
 *
 */
public class Singleton_1 {

    private static Singleton_1 instance = null;

    private Singleton_1() {
    }

    public static Singleton_1 getInstacne() {
        /*
         * 这种实现进行了两次instance==null的判断,这便是单例模式的双检锁。
         * 第一次检查是说如果对象实例已经被创建了,则直接返回,不需要再进入同步代码。
         * 否则就开始同步线程,进入临界区后,进行的第二次检查是说:
         * 如果被同步的线程有一个创建了对象实例, 其它的线程就不必再创建实例了。
         */
        if (instance == null) {
            synchronized (Singleton_1.class) {
                if (instance == null) {
                    /*
                     * 仍然存在的问题:下面这句代码并不是一个原子操作,JVM在执行这行代码时,会分解成如下的操作:
                     * 1.给instance分配内存,在栈中分配并初始化为null
                     * 2.调用Singleton_1的构造函数,生成对象实例,在堆中分配 
                     * 3.把instance指向在堆中分配的对象
                     * 由于指令重排序优化,执行顺序可能会变成1,3,2,
                     * 那么当一个线程执行完1,3之后,被另一个线程抢占,
                     * 这时instance已经不是null了,就会直接返回。
                     * 然而2还没有执行过,也就是说这个对象实例还没有初始化过。
                     */
                    instance = new Singleton_1();
                }
            }
        }
        return instance;
    }
}
/**
 * 单例模式例程二
 * 
 * @author Colin Wang
 *
 */
public class Singleton_2 {

    /*
     * 为了避免JIT编译器对代码的指令重排序优化,可以使用volatile关键字,
     * 通过这个关键字还可以使该变量不会在多个线程中存在副本,
     * 变量可以看作是直接从主内存中读取,相当于实现了一个轻量级的锁。
     */
    private volatile static Singleton_2 instance = null;

    private Singleton_2() {
    }

    public static Singleton_2 getInstacne() {
        if (instance == null) {
            synchronized (Singleton_2.class) {
                if (instance == null) {
                    instance = new Singleton_2();
                }
            }
        }
        return instance;
    }
}

变量在有了volatile修饰之后,对变量的修改会有一个内存屏障的保护,使得后面的指令不能被重排序到内存屏障之前的位置。volalite变量的读性能与普通变量类似,但是写性能要低一些,因为它需要插入内存屏障指令来保证处理器不会发生乱序执行。即便如此,大多数场景下volatile的总开销仍然要比锁低,所以volatile的语义能满足需求时候,选择volatile要优于使用锁。

本文转自


Java内存模型与volatile关键字


附参考知识

Java 理论与实践: 正确使用 Volatile 变量

Java™ 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错。在这期的 Java 理论与实践 中,Brian Goetz 将介绍几种正确使用 volatile 变量的模式,并针对其适用性限制提出一些建议。

Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。本文介绍了几种有效使用 volatile 变量的模式,并强调了几种不适合使用 volatile 变量的情形。

锁提供了两种主要特性:互斥(mutual exclusion)可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。

Volatile 变量

Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。因此,单独使用 volatile 还不足以实现计数器、互斥锁或任何具有与多个变量相关的不变式(Invariants)的类(例如 “start <=end”)。

出于简易性或可伸缩性的考虑,您可能倾向于使用 volatile 变量而不是锁。当使用 volatile 变量而非锁时,某些习惯用法(idiom)更加易于编码和阅读。此外,volatile 变量不会像锁那样造成线程阻塞,因此也很少造成可伸缩性问题。在某些情况下,如果读操作远远大于写操作,volatile 变量还可以提供优于锁的性能优势。

举报

相关文章推荐

Java内存模型与volatile关键字

Java内存模型(Java Memory Model)Java内存模型(JMM),不同于Java运行时数据区,JMM的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中读取...

Java内存模型&volatile关键字

java内存模型(JMM): 相关概念: 1)在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。 2)java的并发采用的是共享内存模型:通过读/写内存中的公共状态进行隐式通信...
  • A__17
  • A__17
  • 2015-10-30 23:35
  • 329

精选:深入理解 Docker 内部原理及网络配置

网络绝对是任何系统的核心,对于容器而言也是如此。Docker 作为目前最火的轻量级容器技术,有很多令人称道的功能,如 Docker 的镜像管理。然而,Docker的网络一直以来都比较薄弱,所以我们有必要深入了解Docker的网络知识,以满足更高的网络需求。

java并发编程volatile关键字

Java并发编程:volatile关键字解析    volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的...

Java并发编程与内存模型:volatile关键字解析

Java并发编程与内存模型:volatile关键字解析文章链接:知识点: 内存模型的相关概念; 并发编程中的三个概念; Java内存模型; 深入剖析volatile关键字; 使用volatile关键字...

java的内存模型与volatile关键字详解

由于各种硬件及操作系统的内存访问差异,java虚拟机使用java内存模型(java Memory Model,JMM)来规范java对内存的访问。这套模型在jdk 1.2中开始建立,经jdk 1.5的...

volatile与Java内存模型

volatile与Java内存模型volatile与Java内存模型 概述 为什么不同线程不能及时看到同一个变量 volatile关键字 参考资料概述理解Java内存模型对于正确理解程序的运行有积极的...

Java内存模型与volatile

Java内存模型(JMM)         在介绍volatile之前,先介绍一下java内存模型(JMM)。如下图所示:         每个Java线程在运行的过程中,都有一个与之对应...

java内存模型与volatile

很不错的文章,清晰、透彻。原文地址:http://jiangzhengjun.iteye.com/blog/652532 内存模型描述的是程序中各变量(实例域、静态域和数组元素)之间的关系,...

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

本篇主要结合博主个人对Java内存模型的理解以及相关书籍内容的分析作为前提,对JMM进行较为全面的分析,本篇的写作思路是先阐明Java内存区域划分、硬件内存架构、Java多线程的实现原理与Java内存...

Java同步内存模型和Volatile关键字

计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,由于CPU执行速度很快,而从内存读取数据和向内存写入数...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)