volatile 关键字的理解以及使用
1、volatile 关键字的作用
/**
* volatile 关键字的作用
* 1、volatile 是弱化版的synchronized
* 2、保证可见性(多个线程操作同一个变量,不同 的线程能共享变量,一个线程修改了值,另一个线程能看到)
* 3、不保证原子性(原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。)
* 多个线程操作一个变量,其中一个线程操作的时候可能会被别的线程插队。
* 4、禁止指令重排。
* volatile用的最多的地方就是单例模式的双重锁模式里面
*
*
*/
2、volatile 关键字的可见性
代码中的num 如果不用volatile修饰那么循环会一直执行下去的
/**
* 保证可见性的例子
*/
public class JMMDemo {
private volatile static int num = 0;
public static void main(String[] args) { //这是一个线程
new Thread(()->{ // 如果这里 使用的num不用volatile 修饰,这个线程里面的循环会一直执行
while (num == 0){}
},"gao").start();
try{
TimeUnit.SECONDS.sleep(2);
}catch (InterruptedException e){
}
num = 1; //主线程修改值
System.out.println(num);
}
}
2、volatile 不保证原子性的例子
/**
* 一、num++ 是不是一个原子性操作
* num++ 不是一个原子性的操作,导致最后的结果比理论的20000 小。如果是20000 那么证明num++ 是原子性的
* 二、保证测试结果是20000 的解决办法:
* 1、 加synchronized 锁
* 2、 用 lock
* 3、 使用原子类
* 三、使用volatile 保证不了结果是20000的原因
* 原因是volatile 保证不了原子性,如果最后的结果是 20000 那么就能说明volatile 能保证原子性
* 四、num ++ 的底层操作
* 使用 Javap -c 这个命令对num++ 的编译后的代码进行反编译 num++ 会有好几个操作,导致最后的结果不是20000;
*/
// 不保证原子性的demo
public class YZXDemo {
private volatile static int num = 0;
//private static AtomicInteger num = new AtomicInteger(0);
public static void add(){
num++;
//num.getAndIncrement(); //底层用的是CAS
}
public static void main(String[] args) {
//正常理解结果是20000实际结果小于20000
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000;j++){
add();
}
}).start();
}
// 判断下存活的线程数 java 默认两个线程jc和 main
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(num);
}
}

3、指令重排
/** * * 一、指令重排是什么: * 指令重排是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序. * 可能写1000万行代码也不会出现指令重排 * * 二、volatile 避免指令重排的原因 * 添加一个内存的屏障从而禁止指令的重排 * * 三、volatile 哪里用的最多 * 单例模式的双重锁模式时 * * */
public class ZLCPDemo {
int a = 0;
boolean flag = false;
public void writer() {
// 以下两句执行顺序可能会在指令重排等场景下发生变化
a = 1;
flag = true;
}
public void reader() {
if (flag) {
int i = a + 1;
}
}
}
浙公网安备 33010602011771号