并发编程实战第三章(二)

来源:互联网 发布:光大证券mac版 编辑:程序博客网 时间:2024/06/05 11:18

                                                                                              Volatile关键字解读及案例

   在上篇文中指出Volatile的轻量、读取拿到的是最新写入值、严格的使用条件、只能保证可见性。本篇中主要加深对其理解和使用demo。

   1、Java中的内存模型

   在理解之前,要能先了解Java的内存模型。明确在内存模型中有严格的区分:主内存和线程内存。主内存是线程间共享的,比如堆内存。线程内存是指每个线程都会有工作内存,工作内存是不能共享,即其他线程不可以访问的,且对于数据操作只能在工作内存中完成。工作内存中的数据是从主内存中拷贝而来的,线程间数据交换必须通过主内存来完成。内存模型如下图所示:

   

  在上述的内存模型中,会出现脏数据的问题。

  线程对主内存数据的修改过程,总结:线程内存对主内存的数据更新不是原子操作。(这一点必须明确,因为这是为什么Volatile不能保证原子性的原因)。可分为以下步骤:

  1、从主内存中拷贝到线程的工作内存——读取

  2、线程读取后的状态具有不确定性,可能处于阻塞状态或立刻对工作内存中进行修改并写入主内存

 

 2、可见性、原子性、有序性

  先来了解Java并发编程中的三个基本的概念:可见性、原子性、有序性

  可见性:这里是指,线程工作内存对主内存中的数据进行修改,并将修改的写入到主内存中,其他线程能否及时看到主内存中数据已经有变化。

  原子性:对于单个或多个操作,要么都成功,要么都失败。

  有序性:在JVM中在满足数据依赖的基础上,编译器、处理器允许指令重排序,即不按照代码由上到下顺序执行。

   在Java中的有序性8个原则是:

    

    ①程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作


    ②锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作


   ③volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作


   ④传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C


   ⑤线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作


   ⑥线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生


   ⑦线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行


   ⑧对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始


   这8条规则中,前4条规则是比较重要的,后4条规则都是显而易见的。

  3、volatile关键字

  当一个共享变量被声明为Volatile变量后,应当明确改变具备的特性是:

  1、一旦有一个线程对共享变量的值修改了并完成对主内存的写入,那么其他缓存该变量的线程都会被迫更新为主内存的值。

  2、不能保证原子性。(本质原因是线程内存与主内存之间的数据传递不是原子操作,共有8个原子操作)

  示例代码:

    

/** * 验证Volatile只能保证可见性,不能确保原子性 * * @author caiqiang * @version 2017年4月10日 * @see VolatileTest * @since */public class VolatileTest {    // Volatile关键字修饰,保证可见性    public volatile int inc = 0;    // 自增,对于自增i++不是原子操作    public synchronized void increase() {        inc++ ;    }    public static void main(String[] args) {        final VolatileTest test = new VolatileTest();        // 该线程循环执行10次        for (int i = 0; i < 10; i++ ) {            new Thread() {                // 线程1000次自增                public void run() {                    for (int j = 0; j < 1000; j++ )                        test.increase();                };            }.start();        }        while (Thread.activeCount() > 1)            // 保证前面的线程都执行完            Thread.yield();        System.out.println(test.inc);    }}
从开发者的角度来看,这个结果应该是10000。此实验是验证Volatile是否能保证原子性,如果实验的结果是10000,则Volatile能保证原子性;如果实验结果是小于10000,则Volatile不能保证原子性。实验的结果几乎都是小于10000(在有限的次数内)。

4、volatile的典型应用:

1、状态标记量

volatile boolean flag = false; //线程1while(!flag){    doSomething();}  //线程2public void setFlag() {    flag = true;}
 该例如果没有volatile会怎样呢?线程1正在执行循环,线程2修改了共享变量flag,但是没有立即写入主内存中,那么会导致线程1还在执行循环。这也就是说,加上volatile关键字会使线程在工作内存的修改会立即写入主内存中。

2、单例模式

class Singleton{    private volatile static Singleton instance = null;     private Singleton() {     }     public static Singleton getInstance() {        if(instance==null) {            synchronized (Singleton.class) {                if(instance==null)                    instance = new Singleton();            }        }        return instance;    }}
参考文章:http://mp.weixin.qq.com/s/JY1totcwH0E-nboQ_I01Rw

   

0 0
原创粉丝点击