美文网首页
java并发 volatile关键字的理解

java并发 volatile关键字的理解

作者: xiazki | 来源:发表于2017-11-23 14:06 被阅读0次

    在多线程的环境,当一个线程修改了共享变量,另一个线程能读取到这个变量的修改值,变量java 提供了volatile保证了变量的可见性,轻量级的synchronized
    它实现的原理主要有以下两个方面

    • 追加的LOCK#指令会使处理器缓存行写回到内存
    • 一个处理器的缓存写回到内存会使其他处理器的缓存无效

    volatile的应用

    先看一段代码,假如线程1先执行,线程2后执行:

    //线程1
    boolean stop = false;
    while(!stop){
        doSomething();
        }
         
      //线程2
      stop = true;
    

    这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程(虽然这个可能性很小,但是只要一旦发生这种情况就会造成死循环了)。
    下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。
    那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
    但是用volatile修饰之后就变得不一样了:

    • 使用volatile关键字会强制将修改的值立即写入主存;
    • 使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
    • 由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
      那么在线程2修改stop值时(当然这里包括2个操作,修改线程2工作内存中的值,然后将修改后的值写入内存),会使得线程1的工作内存中缓存变量stop的缓存行无效,然后线程1读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。
      那么线程1读取到的就是最新的正确的值

    注意

    volatitle保证了共享变量的可见性,但是并没有保证原子性,如果变量额的操作非原子性的,也会线程不安全。

    public class Test {
        public volatile int inc = 0;
         
        public void increase() {
        inc++;
        }
                  
        public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run(){
                    for(int j=0;j<1000;j++)
                        test.increase();
                        };
                    }.start();
                }
                                                                                 
        while(Thread.activeCount()>1)  //保证前面的线程都执行完    
        Thread.yield();
        System.out.println(test.inc);
        }
    }
    
    

    由于自增操作是非原子操作,某一时刻,存在两个线程读取同一个有效的inc 此时由于是非原子操作,此时将进行两次++ 但是导致只发生了一次++操作。

    所以volatile保证了变量的可见性但是不能不能保证线程安全

    Java Current包下的原子类通过CAS(Compare and set)完成同步锁的一种乐观锁(更新时判断是否被修改) 来保证对变量的原子操作从而保证了线程安全。
    如下面代码:

    private volatile int value;
    
    public final int get() {
            return value;
    }
    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
            }
    }
    
    

    相关文章

      网友评论

          本文标题:java并发 volatile关键字的理解

          本文链接:https://www.haomeiwen.com/subject/kujzvxtx.html