java--volatile关键字

转:https://www.cnblogs.com/selene/p/5972882.htmlhtml

volatile不能保证数据同步

volatile关键字比较少用,缘由无外乎两点,一是在Java1.5以前该关键字在不一样的操做系统上有不一样的表现,所带来的问题就是移植性较差;并且比较难设计,并且误用较多,这也致使它的"名誉" 受损。安全

  咱们知道,每一个线程都运行在栈内存中,每一个线程都有本身的工做内存(Working Memory,好比寄存器Register、高速缓冲存储器Cache等),线程的计算通常是经过工做内存进行交互的,其示意图以下图所示:多线程

  

  从示意图上咱们能够看到,线程在初始化时从主内存中加载所需的变量值到工做内存中,而后在线程运行时,若是是读取,则直接从工做内存中读取,如果写入则先写到工做内存中,以后刷新到主内存中,这是JVM的一个简答的内存模型,可是这样的结构在多线程的状况下有可能会出现问题,好比:A线程修改变量的值,也刷新到了主内存,但B、C线程在此时间内读取的仍是本线程的工做内存,也就是说它们读取的不是最"新鲜"的值,此时就出现了不一样线程持有的公共资源不一样步的状况。ide

  对于此类问题有不少解决办法,好比使用synchronized同步代码块,或者使用Lock锁来解决该问题,不过,Java可使用volatile更简单地解决此类问题,好比在一个变量前加上volatile关键字,能够确保每一个线程对本地变量的访问和修改都是直接与内存交互的,而不是与本线程的工做内存交互的,保证每一个线程都能得到最"新鲜"的变量值,其示意图以下:oop

  

  明白了volatile变量的原理,那咱们思考一下:volatile变量是否可以保证数据的同步性呢?两个线程同时修改一个volatile是否会产生脏数据呢?咱们看看下面代码:测试

class UnsafeThread implements Runnable {
    // 共享资源
    private volatile int count = 0;

    @Override
    public void run() {
        // 增长CPU的繁忙程度,没必要关心其逻辑含义
        for (int i = 0; i < 1000; i++) {
            Math.hypot(Math.pow(92456789, i), Math.cos(i));
        }
        count++;
    }

    public int getCount() {
        return count;
    }
}

上面的代码定义了一个多线程类,run方法的主要逻辑是共享资源count的自加运算,并且咱们还为count变量加上了volatile关键字,确保是从内存中读取和写入的,若是有多个线程运行,也就是多个线程执行count变量的自加操做,count变量会产生脏数据吗?想一想看,咱们已经为count加上了volatile关键字呀!模拟多线程的代码以下:spa

public static void main(String[] args) throws InterruptedException {
        // 理想值,并做为最大循环次数
        int value = 1000;
        // 循环次数,防止形成无限循环或者死循环
        int loops = 0;
        // 主线程组,用于估计活动线程数
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        while (loops++ < value) {
            // 共享资源清零
            UnsafeThread ut = new UnsafeThread();
            for (int i = 0; i < value; i++) {
                new Thread(ut).start();
            }
            // 先等15毫秒,等待活动线程为1
            do {
                Thread.sleep(15);
            } while (tg.activeCount() != 1);
            // 检查实际值与理论值是否一致
            if (ut.getCount() != value) {
                // 出现线程不安全的状况
                System.out.println("循环到:" + loops + " 遍,出现线程不安全的状况");
                System.out.println("此时,count= " + ut.getCount());
                System.exit(0);
            }
        }

    }

想让volatite变量"出点丑",仍是须要花点功夫的。此段程序的运行逻辑以下:操作系统

  • 启动100个线程,修改共享资源count的值
  • 暂停15秒,观察活动线程数是否为1(即只剩下主线程再运行),若不为1,则再等待15秒。
  • 判断共享资源是不是不安全的,即实际值与理想值是否相同,若不相同,则发现目标,此时count的值为脏数据。
  • 若是没有找到,继续循环,直到达到最大循环为止。

运行结果以下:线程

    循环到:40 遍,出现线程不安全的状况
    此时,count= 999
  这只是一种可能的结果,每次执行都有可能产生不一样的结果。这也说明咱们的count变量没有实现数据同步,在多个线程修改的状况下,count的实际值与理论值产生了误差,直接说明了volatile关键字并不能保证线程的安全。
  在解释缘由以前,咱们先说一下自加操做。count++表示的是先取出count的值而后再加1,也就是count=count+1,因此,在某个紧邻时间片断内会发生以下神奇的事情:设计

(1)、第一个时间片断

  A线程得到执行机会,由于有关键字volatile修饰,因此它从主内存中得到count的最新值为998,接下来的事情又分为两种类型:

  • 若是是单CPU,此时调度器暂停A线程执行,让出执行机会给B线程,因而B线程也得到了count的最新值998.
  • 若是是多CPU,此时线程A继续执行,而线程B也同时得到了count的最新值998.

(2)、第二个片断

  • 若是是单CPU,B线程执行完+1操做(这是一个原子处理),count的值为999,因为是volatile类型的变量,因此直接写入主内存,而后A线程继续执行,计算的结果也是999,从新写入主内存中。
  • 若是是多CPU,A线程执行完加1动做后修改主内存的变量count为999,线程B执行完毕后也修改主内存中的变量为999

这两个时间片断执行完毕后,本来指望的结果为1000,单运行后的值为999,这表示出现了线程不安全的状况。这也是咱们要说明的:volatile关键字并不能保证线程安全,它只能保证当前线程须要该变量的值时可以得到最新的值,而不能保证线程修改的安全性。

顺便说一下,在上面的代码中,UnsafeThread类的消耗CPU计算是必须的,其目的是加剧线程的负荷,以便出现单个线程抢占整个CPU资源的情景,不然很难模拟出volatile线程不安全的状况,你们能够自行模拟测试。

相关文章
相关标签/搜索