Volatile 关键字浅析解析

1. volatile的定义
Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致性地更新,线程应该确保通过排他锁单独获取这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile关键字,Java线程内存模型确保所有线程看到这个变量值的一致性。
2.volatile的实现原则
1)Lock前缀指令会引起处理器缓存写回内存。Lock前缀指令导致在执行指令期间,声言处理器的Lock#信号。在多核处理器环境中,Lock#信号确保在声言该信号期间,处理器可以独占任何共享内存。
2)一个处理器的缓存写会内存会导致其他处理器的缓存失效。(根据MESI协议
3.volatile的自身特性(自身角度分析特性)
理解volatile特性的一个好方法是把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步。如下两个代码示例:
volatile关键字代码:

public class VolatileFeaturesExample {

    volatile long v1 = 0L;

    public void set (long v2) {
        this.v1 = v2;
    }

    public long get () {
        return v1;
    }

    public void getAndIncrement() {
        v1++;
    }

    public static void main(String[] args) {
        VolatileFeaturesExample v = new VolatileFeaturesExample();
        /*new Thread(new ThreadSet(v)).start();
        new Thread(new ThreadGet(v)).start();*/
        final CountDownLatch countDownLatch = new CountDownLatch(5000);
        for (int i = 0;i < 5000;i ++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    v.getAndIncrement();
                    countDownLatch.countDown();
                }
            }).start();
        }
        try {
            countDownLatch.await();
            System.out.println(v.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class ThreadGet implements Runnable {
        private VolatileFeaturesExample v;

        public ThreadGet (VolatileFeaturesExample v) {
            this.v = v;
        }

        public void run() {
            long local_v1 = 0L;
            while (local_v1 <10) {
                if (local_v1 != v.get()) {
                    System.out.println("ThreadGet--------------"+v.get());
                    local_v1 = v.get();
                }
            }

        }

    }

    static class ThreadSet implements Runnable {

        private VolatileFeaturesExample v;

        public ThreadSet (VolatileFeaturesExample v) {
            this.v = v;
        }

        public void run() {
            long local_v1 = 0L;
            while (local_v1 < 10) {
                System.out.println("ThreadSet----------"+(++local_v1));
                v.set(local_v1);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

把volatile改为锁synchronized。(这里只贴了方法代码,其它和上面一样)

long v1 = 0L;

    public synchronized void set (long v2) {
        this.v1 = v2;
    }

    public synchronized long get () {
        return v1;
    }

代码分析:
get和set方法执行结果
(1)加了volatile关键字的执行结果

ThreadSet----------1
ThreadGet--------------1
ThreadSet----------2
ThreadGet--------------2
ThreadSet----------3
ThreadGet--------------3
ThreadSet----------4
ThreadGet--------------4
ThreadSet----------5
ThreadGet--------------5
ThreadSet----------6
ThreadGet--------------6
ThreadSet----------7
ThreadGet--------------7
ThreadSet----------8
ThreadGet--------------8
ThreadSet----------9
ThreadGet--------------9
ThreadSet----------10
ThreadGet--------------10

(2)去掉volatile执行结果

ThreadSet----------1
ThreadGet--------------1
ThreadSet----------2
ThreadSet----------3
ThreadSet----------4
ThreadSet----------5
ThreadSet----------6
ThreadSet----------7
ThreadSet----------8
ThreadSet----------9
ThreadSet----------10

(3)去掉volatile关键字,换成锁synchronized的执行结果

ThreadSet----------1
ThreadGet--------------1
ThreadSet----------2
ThreadGet--------------2
ThreadSet----------3
ThreadGet--------------3
ThreadSet----------4
ThreadGet--------------4
ThreadSet----------5
ThreadGet--------------5
ThreadSet----------6
ThreadGet--------------6
ThreadSet----------7
ThreadGet--------------7
ThreadSet----------8
ThreadGet--------------8
ThreadSet----------9
ThreadGet--------------9
ThreadSet----------10
ThreadGet--------------10

getAndIncrement执行结果
(4)使用带有volatile关键字的v1,调用getAndIncrement累加到5000

第一次:5000
第二次:500
第三次:4999
第四次:5000
第五次:4999

如上面示例代码所示,一个volatile变量的读/写操作,与一个普通变量的读/写使用锁同步,它们之间的执行结果不同。不使用volatile关键字,发现一个线程改线,另一个线程可能都不可见。
锁的happens-before规则保证释放锁和获取锁的两个线程之间的内存可见性,这意味着对于一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
锁的语义决定了临界区代码执行具有原子性。这意味,即使64位的long型和double变量,只要它是volatile变量,对该该变量的读/写就具有原子性。根据代码getAndIncrement方法结果得知,对于volatile++这种复合操作不就有原子性,这些操作本身不具有原子性。
特性
(1)可见性。对于一个volatile变量的读,总是能看到(任意线程)对这个volatile变量的最后写入。
(2)原子性。对任意单个volatile变量的读/写具有原子性,但类似volatile++这种操作不具有原子性。
4.volatile特性的影响性(从不是volatile变量的角度分析,volatile给它们带来的内存可见性影响)
1)volatile 写/读建立的happens-before关系
其实volatile保证了可见性,其实就是完成了线程之间的通信。
我们来分析下如下代码的happens-before关系

int num = 0;
    volatile boolean flag = false;

    public void write (int i) {
        num = i;  // 1
        flag = true;// 2
    }

    public  int read () {
        if (flag) { // 3
            int i = num; // 4
            return i;
        }
        return num;
    }

(1)根据程序次序规则,1happens-before2;3happens-before4;
(2)根据volatile规则,2happens-before3;
(3)根据happens-before的传递规则,1happens-before4;
我们发现一个问题volatile影响了普通的字段,可以理解为write的普通num写对read的普通读num可见了,根据1happens-before来判断。
代码测试,测试volatile对普通变量的影响:
影响特性
(1)任何变量的写在volatile变量写之前,那么这个变量在volatile变量读之后是可见的.(具体解释在实现原理中)
5.volatile内存语义实现
1)volatile重排序规则表
(1)当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile之后。
(2)当第一个是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile之前。
(3)当第一个操作是volatile写,第二个操作volatile时,不能进行重排序。
2)限制重排序的规则(内存屏障)
(1)在每个volatile写操作的前面插入一个StoreStore屏障。
(2)在每个volatile写操作的后面插入一个StoreLoad屏障。
(3)在每个volatile读操作的后面插入一个LoadLoad屏障。
(4)在每个volatile读操作的后面插入一个LoadStore屏障。
上面的内存屏障都非常保守,但它可以保证任意处理器平台,任意程序中都能得到正确的volatile语义。
3)代码示例分析
(1)volatile写插入的内存屏障
内存语义:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
实现写语义的内存屏障:StoreStore和StoreLoad。如下图执行指令执行顺序

StoreStore屏障可以保证在volatile写之前,前面所有的普通写操作已经对任何处理器可见了。这是因为StoreStore屏障将保证上面所有的普通写在volatile写之前刷新到主内存。
这里比较有意思的是,volatile写后面的StoreLoad屏障。此屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。因为编译器常无法准确判断一个volatile写的后面是否需要插入一个StoreLoad屏障。为了保证能正确实现volatile的内存语义,JMM实现了保守策略:在每个volatile写的后面或者每个volatile读前面插入一个StoreLoad屏障。从整体的执行效率角度考虑,JMM最终选择了在在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见模式是:一个线程写volatile变量,读个线程读取volatile读取同一个volatile变量。当读线程的数量大大超过写线程时,选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。
(2)volatile读插入的内存屏障

LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。

原文地址:https://blog.51cto.com/14220760/2376454

时间: 2024-07-31 12:21:25

Volatile 关键字浅析解析的相关文章

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

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

Java并发编程 Volatile关键字解析

volatile关键字的两层语义 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的. 2)禁止进行指令重排序. 根据volatile的语义,我们可以看到,volatile主要针对的是并发三要素(原子性,可见性和有序性)中的后两者有实际优化作用. 可见性: 线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作.

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

Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatil

从根源上解析 Java volatile 关键字的实现

1.解析概览 内存模型的相关概念 并发编程中的三个概念 Java内存模型 深入剖析Volatile关键字 使用volatile关键字的场景 2.内存模型的相关概念 缓存一致性问题.通常称这种被多个线程访问的变量为共享变量. 也就是说,如果一个变量在多个CPU中都存在缓存(一般在多线程编程时才会出现),那么就可能存在缓存不一致的问题. 为了解决缓存不一致性问题,通常来说有以下2种解决方法: 通过在总线加LOCK#锁的方式 通过缓存一致性协议 这2种方式都是硬件层面上提供的方式. 上面的方式1会有一

Java并发编程:volatile关键字解析 和双重检查(Double-Check)

转载:http://www.cnblogs.com/dolphin0520/p/3920373.html Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的

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

Java并发编程:volatile关键字解析 (点击链接原文) volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分

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

volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatile关键字的实现原理,最后给出了几个使用vola

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

volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatile关键字的实现原理,最后给出了几个使用vola

volatile关键字解析

转载:http://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来