Java 数组线程间可见性问题

并发编程中通常使用 volatile 保证线程间可见性,但是被 volatile 修饰的数组中元素是无法保证线程间可见的,例如 ConcurrentHashMap 对这一问题采用 Unsafe 的方式访问 U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);

关于 volatile 示例

暂无关于 volatile array 失败测试用例

解释

在 Java 中无论是基础类型数组还是对象类型数组底层都是一个对象,区别是数组对象在对象头中多出 32 位数组长度标识。

普通对象需要保证线程间可见通常修饰 volatile 关键字,若对象内成员未修饰 volatile 关键字,成员是无法保证线程间可见的。在初始化数组或者创建数组是无法为元素修饰 volatile 关键字的,同样的解释还有:

这个是因为Java数组在元素层面的元数据设计上的缺失,无法表达元素是final、volatile等语义,所以开了后门,使用getObjectVolatile用来补上无法表达元素是volatile的坑,@Stable用来补上final的坑,数组元素就跟没有标volatile的成员字段一样,无法保证线程之间可见性。
链接:https://www.jianshu.com/p/5808db3e2ace

保证数组元素可见性的几种方式

Unsafe.getVolatileObject

public class VolatileArray {

    private volatile Object[] nums = new Object[]{new Object()};
    private static Unsafe U;
    private static long NUMS;

    static {
        try {
            Class<?> clazz = Class.forName("sun.misc.Unsafe");
            Field theUnsafe = clazz.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            U = (Unsafe) theUnsafe.get(null);
            NUMS = U.objectFieldOffset(VolatileArray.class.getDeclaredField("nums"));
        } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {

        VolatileArray example = new VolatileArray();
        new Thread(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            example.nums[0] = new Object();
        }).start();

        new Thread(() -> {
            while (true) {
                Object[] objects = (Object[]) U.getObjectVolatile(example, NUMS);
                if (objects[0] != null) {
                    System.out.println("index updated");
                    break;
                }
            }
        }).start();
    }

}

AtomicReferenceArray

public class VolatileArray {

    private AtomicReferenceArray<Object> atomicReferenceArray = new AtomicReferenceArray<>(1);

    public static void main(String[] args) throws InterruptedException {

        VolatileArray example = new VolatileArray();
        new Thread(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            example.atomicReferenceArray.set(0, new Object());
        }).start();

        new Thread(() -> {
            while (true) {
                if (example.atomicReferenceArray.get(0) != null) {
                    System.out.println("index updated");
                    break;
                }
            }
        }).start();
    }

}

AtomicReferenceArray 实现与上方 Unsafe 代码思想基本一致。

posted @ 2020-08-03 14:12  Haoyo  阅读(549)  评论(0)    收藏  举报