什么是volatile
volatile是一种比 sychronized
关键字更轻量级的同步机制,访问 volitile
变量时,不会执行加锁操作。
volatile的特性
(1)保证将子线程内的数据立即刷新到主内存中。保证了线程间共享变量的及时可见性,但不能保证原子性
(2)禁止指令重排序优化
(3)volatile修饰的long 与double 可以使得这两个类型变成原子类操作
特性验证
(1)保证线程间的可见性
/**
* @Author: lin.shi
* @Date: 2020/8/26 7:17 下午
* @Describe: 验证volatile 的可见性
*/
public class VolatileDemo1 {
public static void main(String[] args) {
visibilityByVolatile();//验证volatile的可见性
}
/**
* volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改
*/
public static void visibilityByVolatile() {
MyData myData = new MyData();
//第一个线程
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
//线程暂停3s
TimeUnit.SECONDS.sleep(3);
myData.addToSixty();
System.out.println(Thread.currentThread().getName() + "\t update value:" + myData.num);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}, "thread1").start();
//第二个线程是main线程
while (myData.num == 0) {
//如果myData的num一直为零,main线程一直在这里循环 }
System.out.println(Thread.currentThread().getName() + "\t mission is over, num value is " + myData.num);
}
}
static class MyData {
//未加关键字volatile,主线程将一直死循环
int num = 0;
//主线程不在卡死
// volatile int num = 0;
public void addToSixty() {
this.num = 60;
}
}
}
(2)volatile不保证原子性
/**
* @Author: lin.shi
* @Date: 2020/8/27 10:30 下午
* @Describe:
*/
public class VolatileDemo2 {
public static void main(String[] args) {
// visibilityByVolatile();//验证volatile的可见性
atomicByVolatile();//验证volatile不保证原子性
}
/**
* volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改 */
//public static void visibilityByVolatile(){}
/**
* volatile不保证原子性
* 以及使用Atomic保证原子性
*/
public static void atomicByVolatile() {
MyData myData = new MyData();
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
myData.addSelf();
myData.atomicAddSelf();
}
}, "Thread " + i).start();
}
//等待上面的线程都计算完成后,再用main线程取得最终结果值
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t finally num value is " + myData.num);
System.out.println(Thread.currentThread().getName() + "\t finally atomicnum value is " + myData.atomicInteger);
}
}
class MyData {
// int num = 0;
volatile int num = 0;
public void addToSixty() {
this.num = 60;
}
public void addSelf() {
num++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void atomicAddSelf() {
atomicInteger.getAndIncrement();
}
}
//控制台结果
//1.
main finally num value is 19580
main finally atomicnum value is 20000
//2.
main finally num value is 19999
main finally atomicnum value is 20000
//3.
main finally num value is 18375
main finally atomicnum value is 20000
//num并没有达到20000
为什么volatile不能保证原子性呢?
这是因为在 num++ 的操作被拆分了三个步骤
<1> 先从内存中把num取出来放到寄存器中
<2>然后++,
<3>然后再把i复制到内存中,这需要至少3步
所以即使volatile能够及时的将最终计算的记过刷新到主内存,但是有可能在执行这三个步骤的过程中,有两个或者多个线程拿到同一个num数据执行这三个步骤中的其中一步,然后在刷新到主内存。这样就是出现num值出现多次重复覆盖。
Atomic原子类可以解决这个问题。
如果您有心看看源码,就会看到如下代码。cas自旋锁了解一下。
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}