很显然,volatile关键字修饰的对象修改后,都能在任何线程中立马拿到。
但是你是否注意到了volatile使用上面的2不能1必须呢?
1、不能将volatile使用到频繁更新的值上面
如下图演示
public static void main(String args[]) throws InterruptedException {
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
b++;
}
}
}).start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(b);
}
按照道理,我们应该最后输出20000,但是最后输出的结果总是小于20000,且每次不固定 。
究其原因,就是因为 对于某线程A,虽然每次我再进行b++操作之前,都能拿到内存中最新的值,但是我在进行b++之后是要把数据写回到内存中的。 而这时候可能内存中的值已经被其他线程增加了好多次
2、volatile 对象不能与其他非volatile对象联合使用,
例如 c=(volatile b+d);使用C的时候,及时b及时改变了,C值也不会改变
3、jvm指令重排会影响业务的标志性对象 必须使用volatile
例如
private static boolean b = false;
private static void changeFlag() {
//doSomeThingCost 10 seconds //代码1
b = true; //代码2
}
private static void sayHello() {
while (true) {
if(b){
System.out.println("hello");
}
}
}
public static void main(String args[]) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
changeFlag();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
sayHello();
}
}).start();
}
如上代码,AB线程基本同时执行,B线程要等A中的代码1执行完后再执行输出hello . 但是由于JVM的指令重排,可能会导致代码2在代码1之前执行,导致提前输出hello。
这样的话,如果是银行等支付付款业务后续处理逻辑,那影响就太大了 。如果加了volatile,那么JVM的指令重排永远不能把代码1放到代码2后面
顺便说一句,内存更新数据是语句级别的,不是方法级别的
验证代码如下,输出为hello= false; 第一个Thread执行了changeFlag(),然后run()并没有执行完,b已经修改为true了。
private volatile static boolean b = false;
private static void changeFlag() {
b = true;
}
private static void sayHello() {
System.out.println("hello=" + b);
}
public static void main(String args[]) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
changeFlag();
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
b = false;
}
}).start();
Thread.sleep(1000);
new Thread(new Runnable() {
@Override
public void run() {
sayHello();
}
}).start();
}