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
}
说点什么
3 评论 在 "volatile"
鑫爷18年就精通这些了,我他吗不学了,>_<
放屁,只是了解。。
顶不住啊,我来晚了星爷