答案:能。
Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不是整个数组。如果改变引用指向的数组,将会受到 volatile 的保护,但是如果多个线程同时改变数组的元素,volatile 标示符就不能起到之前的保护作用了。
volatile 是 Java 的一个关键字,它是 Java 虚拟机提供的轻量级的同步机制,volatile 有三个特性:
(1)保证可见性
(2)不保证原子性
(3)禁止指令重排
下面通过示例演示,在不使用 volatitle 关键的情况下,线程修改了主内存中的数据,对主内存是不可见的,代码如下:
public class Demo { class Data { private int index = 0; public void setIndex(int index) { this.index = index; } public int getIndex() { return this.index; } } public Demo() { final Data data = new Data(); // 在线程中修改数据 new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); System.out.println("线程开始执行..."); data.setIndex(10); System.out.println("线程执行结束"); } catch (Exception e) { e.printStackTrace(); } } }).start(); while(data.getIndex() == 0) { // 如果进入这里,表示不可见 } System.out.println("index 是可见的"); } public static void main(String[] args) { new Demo(); } }
输出结果如下:
线程开始执行... 线程执行结束
从上面的出处结果可知,即使我们在线程中通过 setIndex() 方法将 Data 中的 index 设置为 10,主线程中的 while 循环也没有结束。这是因为,线程中的修改对主线程是不可见的。此时,我们使用 volatile 关键字去修饰 index 属性,让他可见。代码如下:
public class Demo { class Data { private volatile int index = 0; public void setIndex(int index) { this.index = index; } public int getIndex() { return this.index; } } public Demo() { final Data data = new Data(); // 在线程中修改数据 new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); System.out.println("线程开始执行..."); data.setIndex(10); System.out.println("线程执行结束"); } catch (Exception e) { e.printStackTrace(); } } }).start(); while(data.getIndex() == 0) { // 如果进入这里,表示不可见 } System.out.println("index 是可见的"); } public static void main(String[] args) { new Demo(); } }
输出结果如下:
线程开始执行... 线程执行结束 index 是可见的
前面使用 volatile 修饰单个值起到了作用,那么修饰数组呢?下面通过两个示例来演示。
下面定义了一个 int 数组,且使用 volatile 修饰。而在 while 循环中一直比较数组的 hashCode 是否一直,不一致则跳出循环。当在线程中将变量 array 指向新的数组,此时当前数组的 hashCode 和 历史数组的 hashCode 就不一致,跳出循环。代码如下:
public class Demo { class Data { private volatile int[] array = new int[10]; public void setArray(int[] newArray) { this.array = newArray; } public int[] getArray() { return this.array; } } public Demo() { final Data data = new Data(); int hashCode = data.getArray().hashCode(); // 在线程中修改数据 new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); System.out.println("线程开始执行..."); data.setArray(new int[]{}); System.out.println("线程执行结束"); } catch (Exception e) { e.printStackTrace(); } } }).start(); while(data.getArray().hashCode() == hashCode) { // 如果进入这里,表示不可见 } System.out.println("array 是可见的"); } public static void main(String[] args) { new Demo(); } }
运行结果如下:
线程开始执行... 线程执行结束 array 是可见的
从上面示例可知,当我们修改数组变量执行其他数组时,volatile 是有用的。
下面接着示例一的示例,将在线程中直接修改数组引用改为修改数组元素,代码如下:
public class Demo { class Data { private volatile int[] array = new int[10]; public void setArray(int index, int value) { this.array[index] = value; } public int[] getArray() { return this.array; } } public Demo() { final Data data = new Data(); int hashCode = data.getArray().hashCode(); // 在线程中修改数据 new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); System.out.println("线程开始执行..."); // 修改数组元素 data.setArray(0, 10); System.out.println("线程执行结束"); } catch (Exception e) { e.printStackTrace(); } } }).start(); while(data.getArray().hashCode() == hashCode) { // 如果进入这里,表示不可见 } System.out.println("array 是可见的"); } public static void main(String[] args) { new Demo(); } }
输出结果如下:
线程开始执行... 线程执行结束
从输出结果可知,修改数组元素,volatile 不起作用。