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; } } }