关于volatile关键字的作用,网上有很多解释和dome,但是有一些Dome是运行后达不到效果的。我亲试并整理了下面两个Dome留做笔记。
本文连接:volatile关键字作用及Dome_张三的博客-CSDN博客
volatile的作用1.保证数据的可见性。2防止指命重排序。备注:不能保证原子性
1.保证数据保见性
1.1为什么会有可见性的问题?因为共享对象是共享内存中(这里理解的是放到方法区或堆),每个线程使用的时候会将对象拿到线程内存中(线程栈)。加入volatile关键字后,线程会每次使用前去共享内存中拿,做完写操作也会及时的写入,从而保证数据的可见性。附带一个Dome。
图来自百度百科
1.2Dome解释:
不加volatile时,子线程修改了flag的状态后,主线程取不到最新变量,会一直什么都不打印 加volatile时,子线程修改了flag的状态后,主线程取得到最新变量,会一直打印flag is true
Dome可以去掉volatile关键字来试一下结果。
package com.example.demo.jvm;
public class VolatileVisible {
/**
*全局变量每个线程都能用
*/
private static /*volatile*/ boolean flag = false;
/**a
* 不加volatile时,子线程修改了flag的状态后,主线程取不到最新变量,会一直什么都不打印
* 加volatile时,子线程修改了flag的状态后,主线程取得到最新变量,会一直打印flag is true
* @param args
*/
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
flag = true;
System.out.println("flag=" + flag);
}
}).start();
while (true) {
if (flag) {
System.out.println("flag is true");
}
}
}
}
2防止内存的重排序
2.1什么是重排序:CPU为了提升性能,有时候会对我们的指命进行重排序,当时是指的未相互引用同样数据的指令,否则就结果问题。比如我们执行一个 User user = new User();User构造方法有一句打印语句。这时候我们的指令会被拆分成1.new 内存空间,2执行构造方法中的打印语句,3赋值给user对象。这时候CPU为了性能可能执行的是1,3,2这样的语句。如果是单线程那这样问题不大,但是在多线程共享的对象的话则会产生问题,因为其它线程会认为user被赋值的时候构造函数已经执行完了。这时就发生了指命的重排序,这时候我们就需要加入volatile。
2.2Dome解释
* 两个线程,每个线程操作两个变量。t11代表线程1的第一个变量,以此类推。 * 如果出现t12和t22同时为零则说明cpu没有按编写顺序去执行,即cpu发生了重排序。 * 代码逻辑解释: * 先以cpu不会乱序来看, * t12为0时,说明线程1执行完第二句话时,线程2还没有执行,这时线程2的代码去执行. * 以这种顺序来看,t22必然不能为0。如果为0则说明cpu没有按我们预想的顺序去执行,即发生了重排序。
我们不带voliatile时,执行一段时间会出现重排序,加入voliatile后不会出现。可以用代码中的注释调试一下。
package com.example.demo.jvm;
public class VolatileReOrder {
/**
* 两个线程 各两个变量
*/
private static /*volatile*/ int t11 = 0,t12 = 0, t21 = 0,t22 = 0;
public static void main(String[] args) throws InterruptedException {
cpuReOrder();
}
/**
* 两个线程,每个线程操作两个变量。t11代表线程1的第一个变量,以此类推。
* 如果出现t12和t22同时为零则说明cpu没有按编写顺序去执行,即cpu发生了重排序。
* 代码逻辑解释:
* 先以cpu不会乱序来看,
* t12为0时,说明线程1执行完第二句话时,线程2还没有执行,这时线程2的代码去执行.
* 以这种顺序来看,t22必然不能为0。如果为0则说明cpu没有按我们预想的顺序去执行,即发生了重排序。
* @throws InterruptedException
*/
private static void cpuReOrder() throws InterruptedException {
int i = 0;
for(;;){
i++;
t12=0;t11=0;t21=0;t22=0;//恢复变量
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
t11 = 1;
t12 = t21;
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
t21 = 1;
t22 = t11;
}
});
t1.start();t2.start();
t1.join();t2.join();//等待执行完毕
if(t12 == 0 && t22 == 0){
System.out.println("出现重排序i="+i);
break;
}
}
}
}
重排序比较难复现,去掉voliatile关键字执行很长时间才行。截图: