Java并发编程:JMM和volatile关键字

转载请标明出处:
http://blog.csdn.net/forezp/article/details/77580491
本文出自方志朋的博客html

Java内存模型

随着计算机的CPU的飞速发展,CPU的运算能力已经远远超出了从主内存(运行内存)中读取的数据的能力,为了解决这个问题,CPU厂商设计出了CPU内置高速缓存区。高速缓存区的加入使得CPU在运算的过程当中直接从高速缓存区读取数据,在必定程度上解决了性能的问题。但也引发了另一个问题,在CPU多核的状况下,每一个处理器都有本身的缓存区,数据如何保持一致性。为了保证多核处理器的数据一致性,引入多处理器的数据一致性的协议,这些协议包括MOSI、Synapse、Firely、DragonProtocol等。c++

JMM内存模型.png

JVM在执行多线程任务时,共享数据保存在主内存中,每个线程(执行再不一样的处理器)有本身的高速缓存,线程对共享数据进行修改的时候,首先是从主内存拷贝到线程的高速缓存,修改以后,而后从高速缓存再拷贝到主内存。当有多个线程执行这样的操做的时候,会致使共享数据出现不可预期的错误。web

举个例子:编程

i++;//操做缓存

这个i++操做,线程首先从主内存读取i的值,好比i=0,而后复制到本身的高速缓存区,进行i++操做,最后将操做后的结果从高速缓存区复制到主内存中。若是是两个线程经过操做i++,预期的结果是2。这时结果然的为2吗?答案是否认的。线程1读取主内存的i=0,复制到本身的高速缓存区,这时线程2也读取i=0,复制到本身的高速缓存区,进行i++操做,怎么最终获得的结构为1,而不是2。多线程

为了解决缓存不一致的问题,有两种解决方案:并发

  • 在总线加锁,即同时只有一个线程能执行i++操做(包括读取、修改等)。
  • 经过缓存一致性协议

第一种方式就没什么好说的,就是同步代码块或者同步方法。也就只能一个线程能进行对共享数据的读取和修改,其余线程处于线程阻塞状态。
第二种方式就是缓存一致性协议,好比Intel 的MESI协议,它的核心思想就是当某个处理器写变量的数据,若是其余处理器也存在这个变量,会发出信号量通知该处理器高速缓存的数据设置为无效状态。当其余处理须要读取该变量的时候,会让其从新从主内存中读,而后再复制到高速缓存区。ide

编发编程的概念

并发编程的有三个概念,包括原子性、可见性、有序性。svg

原子性

原子性是指,操做为原子性的,要么成功,要么失败,不存在第三种状况。好比:函数

String s="abc";

这个复杂操做是原子性的。再好比:

int i=0;
i++;

i=0这是一个赋值操做,这一步是原子性操做;那么i++是原子性操做吗?固然不是,首先它须要读取i=0,而后须要执行运算,写入i的新值1,它包含了读取和写入两个步骤,因此不是原子性操做。

可见性

可见性是指共享数据的时候,一个线程修改了数据,其余线程知道数据被修改,会从新读取最新的主存的数据。
举个例子:

i=0;//主内存

i++;//线程1

j=i;//线程2

线程1修改了i值,可是没有将i值复制到主内存中,线程2读取i的值,并将i的值赋值给j,咱们指望j=1,可是因为线程1修改了,没有来得及复制到主内存中,线程2读取了i,并赋值给j,这时j的值为0。
也就是线程i值被修改,其余线程并不知道。

有序性

是指代码执行的有序性,由于代码有可能发生指令重排序(Instruction Reorder)。

Java 语言提供了 volatile 和 synchronized 两个关键字来线程代码操做的有序性,volatile 是由于其自己包含“禁止指令重排序”的语义,synchronized 在单线程中执行代码,不管指令是否重排,最终的执行结果是一致的。

volatile详解

volatile关键字做用

被volatile关键字修饰变量,起到了2个做用:

1.某个线程修改了被volatile关键字修饰变量是,根据数据一致性的协议,经过信号量,更改其余线程的高速缓存中volatile关键字修饰变量状态为无效状态,其余线程若是须要重写读取该变量会再次从主内存中读取,而不是读取本身的高速缓存中的。

2.被volatile关键字修饰变量不会指令重排序。

volatile可以保证可见性和防止指令重排

在Java并发编程实战一书中有这样

public class NoVisibility {
    private static boolean ready;
    private static int a;

    public static void main(String[] args) throws InterruptedException {
        new ReadThread().start();
        Thread.sleep(100);
        a = 32;
        ready = true;
      

    }

    private static class ReadThread extends Thread {
        @Override
        public void run() {
            while (!ready) {
                Thread.yield();
            }
            System.out.println(a);
        }
    }
}

在上述代码中,有可能(几率很是小,可是有这种可能性)永远不会打印a的值,由于线程ReadThread读取了主内存的ready为false,主线程虽然更新了ready,可是ReadThread的高速缓存中并无更新。
另外:

a = 32;

ready = true;

这两行代码有可能发生指令重排。也就是能够打印出a的值为0。

若是在变量加上volatile关键字,能够防止上述两种不正常的状况的发生。

volatile不能保证原子性

首先用一段代码测试下,开起了10个线程,这10个线程共享一个变量inc(被volatile修饰),并在每一个线程循环1000次对inc进行inc++操做。咱们预期的结果是10000.

public class VolatileTest {


    public volatile int inc = 0;

    public void increase() {
        inc++;
    }

    public static void main(String[] args) throws InterruptedException {
        final VolatileTest test = new VolatileTest();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++)
                    test.increase();
            }).start();
        }
        //保证前面的线程都执行完
        Thread.sleep(3000);
        System.out.println(test.inc);
    }

}

屡次运行main函数,你会发现结果永远都不会为10000,都是小于10000。可能有这样的疑问,volatile保证了共享数据的可见性,线程1修改了inc变量线程2会从新从主内存中从新读,这样就能保证inc++的正确性了啊,可为何没有获得咱们预期的结果呢?

在以前已经讲述过inc++这样的操做不是一个原子性操做,它分为读、加加、写。一种状况,当线程1读取了inc的值,尚未修改,线程2也读取了,线程1修改完了,通知线程2将线程的缓存的 inc的值无效须要重读,可这时它不须要读取inc ,它仍执行写操做,而后赋值给主线程,这时数据就会出现问题。

因此volatile不能保证原子性 。这时须要用锁来保证,在increase方法加上synchronized,从新运行打印的结果为10000 。

public synchronized void increase() {
        inc++;
}

volatile的使用场景

状态标记

volatile最多见的使用场景是状态标记,以下:

private volatile boolean asheep ;

//线程1
 
while(!asleep){
    countSheep();
}

//线程2
asheep=true;

防止指令重排

volatile boolean inited = false;
//线程1:
context = loadContext();  
inited = true;  
//上面两行代码若是不用volatile修饰,可能会发生指令重排,致使报错
 
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

参考资料

《Java 并发编程实战》

《深刻理解JVM》

海子的博客:http://www.cnblogs.com/dolphin0520/p/3920373.html


扫码关注公众号有惊喜

(转载本站文章请注明做者和出处 方志朋的博客

相关文章
相关标签/搜索