java并发编程,volatile内存实现和原理

前面的博文说了java的内存模型,介绍了java内存模型的基础,此篇文章来讲一下volatile关键字,这个在并发编程中,占有举足轻重地位的关键字。java

在java5.0 以前它是一个备受争议的关键字,5以后它重获新生。volatile关键字的做用是保证多线程中变量的可见性,是JUC包中的核心。编程

在内存模型基础中,已经提到过,JVM是分为堆内存和栈内存的,堆内存在线程之间共享,而栈内存为线程内部私有,对其余线程不可见。为保证变量的可见性,可使用volatile修饰,那为何使用了关键字volatile修饰后,就能保证可见性,下面进行分析。缓存

volatile 能够看做是一个轻量级的锁,这么说多是不许确的,但它确实具有了锁的一些特性。与锁的区别是,锁保证原子性,锁住的多是是个变量或者一段代码,而volatile修饰的变量只能保证变量在线程之间的传递,只能保证可见性,在一些方面并无具有原子性多线程

因此上面的话有两层语义:并发

  • 保证可见性,不保证原子性
  • 禁止指令的重排序(重排序会破坏volatile的内存语义)

volatile变量的读/写,能够实现线程之间的通讯。ide

volatile内存语义

  • 写的内存语义: 当写一个volatile变量时,JMM会把线程对应的本地内存中的共享变量刷新到主内存。
  • 读的内存语义: 当读一个volatile变量时,JMM会把线程对应的本地内存置为无效,线程接下来从主内存中读取共享变量。

初始时,两个线程的本地内存中的flag和a都是初始状态,线程A在写flag变量后,本地内存A中更新过的两个共享变量的值被刷新到主内存中,在读flag变量后,本地内存中包含的值已经被置为无效,此时线程B必须从主内存中读取共享变量。性能

volatile内存语义实现

为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障,来禁止特定类型的处理器重排序。JMM采用了保守策略,规则以下:spa

  • 在每一个volatile写操做的前面插入一个StoreStore屏障
  • 在每一个volatile写操做的后面插入一个StoreLoad屏障
  • 在每一个volatile读操做的前面插入一个LoadLoad屏障
  • 在每一个volatile读操做的后面插入一个LoadStore屏障

从汇编层看volatile关键字

记得曾经看过一篇文章,讲述的是volatile关键字修饰的变量,编程汇编代码后,会在变量的前面插入一条LOCK指令。.net

Java代码: instance = new Singleton();//instance是volatile变量

汇编代码: 0x01a3de1d: movb $0x0,0x1104800(%esi);0x01a3de24: lock addl $0x0,(%esp);

经过上面的代码发现,volatile修饰的变量会多出一个lock指令,LOCK指令属于系统层级: LOCK前缀会使处理器执行当前指令时产生一个LOCK#信号,显示的锁定总线。线程

来看一下LOCK指令的做用:

  • 锁总线:其余cpu对内存的读写请求会被阻塞,直到锁释放,不过由于锁总线的的开销太大,后来采用锁缓存来代替锁总线
  • lock后的写操做会回写已修改的数据,同时让其它cpu相关缓存行失效,从而从新从主存中加载最新的数据
  • 不是内存屏障却完成相似内存屏障的功能,阻止屏障两边的执行重排序

一个示例

先启动一个线程thread,因为isOver=fasle,因此thread里的run方法里的while是一个死循环,而后企图在main线程里改变标志位isOver=true,从而想终止thread里的while死循环,使得thread线程可以结束退出。但是,事与愿违,实验现象是没有中止,即thread里isOver依然为false仍是处于死循环。

public class VolatileDemo {
    private static boolean isOver = false;

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!isOver) ;
            }
        });
        thread.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isOver = true;
    }
}

那么是为何了?怎样解决这种问题。

更正后的代码为:

public class VolatileDemo {
    private static volatile boolean isOver = false;

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!isOver) ;
            }
        });
        thread.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isOver = true;
    }
}

注意不一样点,如今已经将isOver设置成了volatile变量,这样在main线程中将isOver改成了true后,thread的工做内存该变量值就会失效,从而须要再次从主内存中读取该值,如今可以读出isOver最新值为true从而可以结束在thread里的死循环,从而可以顺利中止掉thread线程。如今问题也解决了,知识也学到了:)。

总结

volatile是一种比锁更轻量级的线程之间通讯的机制,volatile仅仅保证对单个volatile变量的读/写具备原子性,而锁的互斥执行的特性能够保证对整个临界区代码执行具备原子性,在功能上,锁比voatile更强大,在可伸缩性和执行性能上,volatile更有优点,可是volatile并不能代替锁。

应用场景

  • 状态标记变量
  • double check

参考:

java并发编程艺术

http://blog.csdn.net/eff666/article/details/67640648