Welcome everyone

volatile

java 汪明鑫 733浏览 3评论

volatile保证可见性不能保证原子性

 

《深入理解java虚拟机》中:


/**
 * volatile变量自增运算测试
 * 
 * @author zzm
 */
public class VolatileTest {

    public static volatile int race = 0;

    public static void increase() {
        race++;
    }

    private static final int THREADS_COUNT = 20;

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        increase();
                    }
                }
            });
            threads[i].start();
        }

        // 等待所有累加线程都结束
        while (Thread.activeCount() > 1)
            Thread.yield();

        System.out.println(race);
    }
}

并没有得到期望的结果

结果小于等于 10000

 

为什么会出现这种情况呢?

因为 ++ 不是一个原子操作

对于 volatile 修饰的变量,LOCK 指令前缀保证的是其 写操作和回写主内存的操作是原子性的 。
什么是写操作?
如:value=10,对变量 value 进行写,这是实实在在的写,是一个原子操作。
而value ++ 不是一个原子操作
可以把 value++ 拆分成以下伪代码:
int tmp = value;    //1
tmp = tmp + 1;      //2
value = tmp;        //3

 

value=temp 才是真正的写操作,LOCK 指令前缀可以保证写操作和回写主内存的操作是原子性的。
而前面两步并没有对 value 进行任何写操作,JVM 不会做出反应,这就是为什么 volatile 不能保证原子性的根本原因。
int tmp = value; tmp = tmp + 1;  可能就会导致丢失部分 ++
比如
tmp = 1
这时 线程1 tmp ++ =2 ,线程2 tmp ++ = 2
同时回写主存,导致value = 2 ,凭空丢失了一次运算

 

 

volatile的应用场景  (常被用作程序的开关)

volatile boolean shutdownRequested;
	
public void shutdown(){
     shutdownRequested=true;
}
	
public void dowork(){
     while(!shutdownRequested){
			
     }
}

 

volatile主要作用是使变量在多个线程间可见

 

volatile的两个作用:

1)使线程本地内存修改后的变量立即刷进主内存

2)使其他线程本地内存中拿到的数据失效,需要重新去主内存中读取

 

 

当然,volatile还可以防止指令的重排序

这个可以参考下面这篇文章

 

 

当程序执行到 volatile 变量的读或写时,在其前面的操作肯定全部已经执行完毕,且结果已经对后面的操作可见;在其后面的操作肯定还没有执行;
在进行指令优化时,不能将在 volatile 变量前面访问的语句放在其后面执行,也不能把 volatile 变量后面的语句放到其前面执行。
OpenJDK 下 unsafe.cpp 源码中,被volatile修饰的变量会存在一个 “lock:”前缀
Lock 指令前缀相当于一个内存屏障(也称内存栅栏),内存屏障主要提供 3 个功能:
•  确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
•  强制将对缓存的修改操作立即写入主存,利用缓存一致性机制,并且缓存一致性机制会阻止同时修改由两个以上 CPU 缓存的内存区域数据;
•  如果是写操作,它会导致其它 CPU 中对应的缓存行无效。
package pers.wmx.demo;

/**
 * @author wmx
 * @date 2020-02-26
 */
public class Singleton {

    private volatile static Singleton instance;

    public static Singleton getInstance()
    {
        if (instance == null)                  //1
        {
            synchronized(Singleton.class) {      //2
                if (instance == null)              //3
                {
                    instance = new Singleton();     //4
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        Singleton instance = getInstance();
        System.out.println(instance);
    }
}

 

javac Singleton.java 

生成class文件

 

javap -c -l Singleton

反编译

警告: 二进制文件Singleton包含pers.wmx.demo.Singleton
Compiled from "Singleton.java"
public class pers.wmx.demo.Singleton {
  public pers.wmx.demo.Singleton();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 7: 0

  public static pers.wmx.demo.Singleton getInstance();
    Code:
       0: getstatic     #2                  // Field instance:Lpers/wmx/demo/Singleton;
       3: ifnonnull     37
       6: ldc           #3                  // class pers/wmx/demo/Singleton
       8: dup
       9: astore_0
      10: monitorenter
      11: getstatic     #2                  // Field instance:Lpers/wmx/demo/Singleton;
      14: ifnonnull     27
      17: new           #3                  // class pers/wmx/demo/Singleton
      20: dup
      21: invokespecial #4                  // Method "<init>":()V
      24: putstatic     #2                  // Field instance:Lpers/wmx/demo/Singleton;
      27: aload_0
      28: monitorexit
      29: goto          37
      32: astore_1
      33: aload_0
      34: monitorexit
      35: aload_1
      36: athrow
      37: getstatic     #2                  // Field instance:Lpers/wmx/demo/Singleton;
      40: areturn
    Exception table:
       from    to  target type
          11    29    32   any
          32    35    32   any
    LineNumberTable:
      line 13: 0
      line 15: 6
      line 16: 11
      line 18: 17
      line 20: 27
      line 22: 37

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #5                  // Method getInstance:()Lpers/wmx/demo/Singleton;
       3: astore_1
       4: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       7: aload_1
       8: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      11: return
    LineNumberTable:
      line 26: 0
      line 27: 4
      line 28: 11
}

 

 

 

 

转载请注明:汪明鑫的个人博客 » volatile

喜欢 (0)

说点什么

3 评论 在 "volatile"

提醒
avatar
排序:   最新 | 最旧 | 得票最多
The Shy
游客

鑫爷18年就精通这些了,我他吗不学了,>_<

222eeee
游客

顶不住啊,我来晚了星爷

wpDiscuz