因为我说:volatile 是轻量级的 synchronized,面试官让我回去等通知! (2)

首先,我们使用 volatile 修饰一个整数变量,再启动两个线程分别执行同样次数的 ++ 和 -- 操作,最后发现执行的结果竟然不是 0,代码如下:

public class VolatileExample { public static volatile int count = 0; // 计数器 public static final int size = 100000; // 循环测试次数 public static void main(String[] args) { // ++ 方式 Thread thread = new Thread(() -> { for (int i = 1; i <= size; i++) { count++; } }); thread.start(); // -- 方式 for (int i = 1; i <= size; i++) { count--; } // 等所有线程执行完成 while (thread.isAlive()) {} System.out.println(count); // 打印结果 } }

以上程序执行结果如下:

1065

可以看出,执行结果并不是我们期望的结果 0,我们把以上代码使用 synchronized 改造一下:

public class VolatileExample { public static int count = 0; // 计数器 public static final int size = 100000; // 循环测试次数 public static void main(String[] args) { // ++ 方式 Thread thread = new Thread(() -> { for (int i = 1; i <= size; i++) { synchronized (VolatileExample.class) { count++; } } }); thread.start(); // -- 方式 for (int i = 1; i <= size; i++) { synchronized (VolatileExample.class) { count--; } } // 等所有线程执行完成 while (thread.isAlive()) {} System.out.println(count); // 打印结果 } }

这次执行的结果变成了我们期望的值 0。

这说明 volatile 只是轻量级的线程可见方式,并不是轻量级的同步方式,所以并不能说 volatile 是轻量级的 synchronized,终于知道为什么面试官让我回去等通知了。

volatile 使用场景

既然 volatile 只能保证线程操作的可见方式,那它有什么用呢?
volatile 在多读多写的情况下虽然一定会有问题,但如果是一写多读的话使用 volatile 就不会有任何问题。volatile 一写多读的经典使用示例就是 CopyOnWriteArrayList,CopyOnWriteArrayList 在操作的时候会把全部数据复制出来对写操作加锁,修改完之后再使用 setArray 方法把此数组赋值为更新后的值,使用 volatile 可以使读线程很快的告知到数组被修改,不会进行指令重排,操作完成后就可以对其他线程可见了,核心源码如下:

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private transient volatile Object[] array; final void setArray(Object[] a) { array = a; } //...... 忽略其他代码 } 总结

本文我们通过代码的方式演示了 volatile 的两大特性,内存可见性和禁止指令重排,使用 ++ 和 -- 的方式演示了 volatile 并非轻量级的同步方式,以及 volatile 一写多读的经典使用案例 CopyOnWriteArrayList。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdzzx.html