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 变量还可以提供优于锁的性能优势。

volatile关键字作用与内存可见性、指令重排序概述[JAVA]

在理解volotile关键字的作用之前,先粗略解释下内存可见性与指令重排序。1. 内存可见性Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存,并且线程只能...
  • t894690230
  • t894690230
  • 2016年01月27日 14:16
  • 2381

JMM——volatile与内存屏障

为了实现volatile内存语义,JMM会分别限制编译器重排序和处理器重排序 1.当第一个操作为普通的读或写时,如果第二个操作为volatile写,则编译器不能重排序这两个操作(...
  • hqq2023623
  • hqq2023623
  • 2016年03月30日 13:08
  • 2835

C/C++中volatile关键字详解

1. 为什么用volatile?     C/C++ 中的 volatile 关键字和 const 对应,用来修饰变量,通常用于建立语言级别的 memory barrier。这是 BS 在 "The...
  • whatday
  • whatday
  • 2016年09月12日 10:03
  • 1406

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

全面理解Java内存模型(JMM)及volatile关键字 标签: java内存模型JMMvolatile并发编程 2017-06-12 11:25 3269人阅读 评论(9) 收藏 举报...
  • w690333243
  • w690333243
  • 2017年06月14日 11:22
  • 245

volatile关键字与Java同步内存模型

java中的volatile关键字,使用并不多,它的主要作用是使变量在线程间可见。volatile的英文意思是“易变的、不稳定的”,这也正是volatile关键字的语义。        假设有两个线程...
  • zsh2050
  • zsh2050
  • 2016年12月07日 22:44
  • 145

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

Java并发编程与内存模型:volatile关键字解析文章链接:知识点: 内存模型的相关概念; 并发编程中的三个概念; Java内存模型; 深入剖析volatile关键字; 使用volatile关键字...
  • qq_16628781
  • qq_16628781
  • 2017年03月24日 20:35
  • 231

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

计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,由于CPU执行速度很快,而从内存读取数据和向内存写入数...
  • LVYINGZHIJIA
  • LVYINGZHIJIA
  • 2016年05月06日 15:25
  • 361

(转)Java并发编程之内存模型与volatile关键字解析

作者:海子      出处:http://www.cnblogs.com/dolphin0520 —————————————————————————分割线—————————————————...
  • jinzhencs
  • jinzhencs
  • 2016年01月20日 15:47
  • 361

并发编程之 Java 内存模型 + volatile 关键字 + Happen-Before 规则

前言楼主这个标题其实有一种作死的味道,为什么呢,这三个东西其实可以分开为三篇文章来写,但是,楼主认为这三个东西又都是高度相关的,应当在一个知识点中。在一次学习中去理解这些东西。才能更好的理解 Java...
  • qq_38182963
  • qq_38182963
  • 2018年01月03日 00:50
  • 49

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

本篇主要结合博主个人对Java内存模型的理解以及相关书籍内容的分析作为前提,对JMM进行较为全面的分析,本篇的写作思路是先阐明Java内存区域划分、硬件内存架构、Java多线程的实现原理与Java内存...
  • u014507083
  • u014507083
  • 2017年06月14日 12:21
  • 191
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Java内存模型与volatile关键字
举报原因:
原因补充:

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