概述
Volatile关键字是JVM提供的轻量级同步机制,其特性为:
1.保证可见性
2.不保证原子性
3.禁止指令重排
保证可见性
示例代码
public class Demo01 {
private static volatile int num = 0;
public static void main(String[] args) {
new Thread(()->{
while (num==0){
};
}).start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num = 1);
}
}
运行效果
主线程执行完 num = 1 语句后就马上结束了,显然此时子线程知晓了num == 1,跳出了while循环,子线程结束,主线程也随之结束
换言之,主线程中对num的修改被子线程知晓了,此为可见性
不保证原子性
原子性:一个操作或者多个操作 要么全部执行 并且执行的过程不会被任何因素打断 要么就都不执行
若要保证原子性: 1.上锁 即synchronized或lock 2.使用原子类 AtomicxXXX
示例代码
public class Demo02 {
//++本身即是非原子性操作
private volatile static int num;
private volatile static AtomicInteger AtomicNum = new AtomicInteger();
private static void add(){
num++;
}
private static void AtomicAdd(){
AtomicNum.addAndGet(1);//底层使用CAS实现
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 10000; j++) {
add();
}
}).start();
}
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 10000; j++) {
AtomicAdd();
}
}).start();
}
//JVM中一直开启的线程 main GC
//所以Thread.activeCount()>2时 可知上述线程尚未执行完
//此处作用是让main线程让出CPU直到上述线程执行完 否则会在上述线程未执行完的情况下就输出执行输出语句
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println("num : " + num);
System.out.println("AtomicNum : " + AtomicNum);
}
}
运行效果
可以看到,num的值明显不符合预期,这是因为num++并不是原子性操作,并发情况下会出现多个线程同时执行num++的情况;假定一时刻num == 100,此时线程A与线程B同时执行num++,A与B均认为num == 100,所以最终二者向主内存提交的结果均为num == 101,导致最终结果缩水
禁止指令重排
指令重排:指令重排序是指编译器或CPU为了优化程序的执行效率 而对不存在数据依赖的指令进行重新排序的一种机制 并发时指令重排可能会导致预期与结果不一致
关于可见性问题的说明:
一.单线程情况下,无可见性问题
public class Demo03 {
public static void main(String[] args) {
int i = 1; //1
int j = 1; //2
int k = i + j;//3
System.out.println(k);
}
}
上述代码中,1与2不存在数据依赖性,所以可能会被指令重排,调换执行顺序;而3数据依赖1与2,所以不可能被指令重排,先于1、2执行
二.多线程情况下,可能引发可见性问题
public class Demo03 {
static int num = 0;
static boolean flag = false;
public static void main(String[] args) {
new Thread(() -> {
if (flag) { //1
num = num + num; //2
System.out.println(num);
}
}).start();
new Thread(() -> {
num = 1; //3
flag = true; //4
}).start();
}
}
若在单线程情况下,1数据依赖4,2数据依赖3,所以3、4必然先于1、2执行,可能的执行顺序为3412或4312;但在多线程情况下,3、4可能会被指令重排,若此时的执行顺序为4123,则会导致输出的结果为0,产生预期与结果不一致的问题