【问题标题】:How to declare array elements volatile in Java?如何在 Java 中声明数组元素 volatile?
【发布时间】:2010-02-10 10:53:33
【问题描述】:

有没有办法在Java中声明数组元素volatile?即

volatile int[] a = new int[10];

声明了数组引用volatile,但数组元素(例如a[1])仍然不是易失性的。所以我正在寻找类似的东西

volatile int[] a = new volatile int[10];

但它不是那样工作的。有可能吗?

【问题讨论】:

  • @Kanagavelu Sugumar:AtomicReference 一个包装好的volatile,带有一些额外的方法(getAndSet 等)。
  • 是的,您可以使数组易失。请参考这个 - javarevisited.blogspot.in/2015/10/…

标签: java arrays concurrency volatile


【解决方案1】:

使用AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray

AtomicIntegerArray 类实现了一个 int 数组,其各个字段可以通过类的 get()set() 方法以 volatile 语义访问。然后从一个线程调用arr.set(x, y) 将保证另一个调用arr.get(x) 的线程将读取值y(直到另一个值被读取到位置x)。

见:

【讨论】:

  • 我想知道为什么对于 int 和 long 有特定的 AtomicArrays,但对于其他原始类型却没有……当然,其余的原始类型可以通过在 @987654334 中使用它们的包装器来伪造@.
  • 我认为 AtomicIntegerArray 和 AtomicLongArray 分别针对整数和长整数进行了优化。
  • @JoonasPulakka 对于其他原始类型,您也可以使用例如将它们转换为 int 或 long Float.floatToIntBits(float)。这避免了在使用AtomicReferenceArray 时需要装箱。
【解决方案2】:

不,您不能使数组元素易失。另见http://jeremymanson.blogspot.com/2009/06/volatile-arrays-in-java.html

【讨论】:

  • 其实可以,但需要付出额外的努力。
  • 从技术上讲,这仍然不会使元素变得易变,但数组操作是易变的。由于这种情况下的int 是一个原语,它本质上具有相同的行为,但是对于非原语数组来说,这不能轻易扩展。
【解决方案3】:

另一种方法是使用 JDK 9+ VarHandle 类。正如您在 AtomicxxxArray 类的源代码中看到的,如 AtomicIntegerArray,这些类也使用 VarHandle,因为 JDK 9:

//[...]

private static final VarHandle AA
    = MethodHandles.arrayElementVarHandle(int[].class);
private final int[] array;

//[...]

/**
 * Returns the current value of the element at index {@code i},
 * with memory effects as specified by {@link VarHandle#getVolatile}.
 *
 * @param i the index
 * @return the current value
 */
public final int get(int i) {
    return (int)AA.getVolatile(array, i);
}

/**
 * Sets the element at index {@code i} to {@code newValue},
 * with memory effects as specified by {@link VarHandle#setVolatile}.
 *
 * @param i the index
 * @param newValue the new value
 */
public final void set(int i, int newValue) {
    AA.setVolatile(array, i, newValue);
}

//[...]

你首先像这样创建一个VarHandle

MethodHandles.arrayElementVarHandle(yourArrayClass)

比如你可以在这里输入byte[].class,自己实现缺少的AtomicByteArray

然后您可以使用setxxx(array, index, value)getxxx(array, index) 方法访问它,其中array 的类型为@ 987654343@,indexint 类型,value 是数组中元素的类型 (yourArrayClass.getComponentType())。

请注意,例如,如果 yourArrayClass == byte[].class 但您将 42 输入为 value,则会出现错误,因为 42int 而不是 byte 和访问方法的参数是可变参数Object... 参数:

java.lang.invoke.WrongMethodTypeException: cannot convert MethodHandle(VarHandle,byte[],int,byte)void to (VarHandle,byte[],int,int)void

(第二个签名是你用的,第一个是你应该用的。)


请注意,在 JDK 8 及以下版本中,sun.misc.Unsafe 用于实现 AtomicIntegerArray 等原子类:

//[...]

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int shift;
private final int[] array;

static {
    int scale = unsafe.arrayIndexScale(int[].class);
    if ((scale & (scale - 1)) != 0)
        throw new Error("data type scale not a power of two");
    shift = 31 - Integer.numberOfLeadingZeros(scale);
}

private long checkedByteOffset(int i) {
    if (i < 0 || i >= array.length)
        throw new IndexOutOfBoundsException("index " + i);

    return byteOffset(i);
}

private static long byteOffset(int i) {
    return ((long) i << shift) + base;
}

//[...]

/**
 * Gets the current value at position {@code i}.
 *
 * @param i the index
 * @return the current value
 */
public final int get(int i) {
    return getRaw(checkedByteOffset(i));
}

private int getRaw(long offset) {
    return unsafe.getIntVolatile(array, offset);
}

/**
 * Sets the element at position {@code i} to the given value.
 *
 * @param i the index
 * @param newValue the new value
 */
public final void set(int i, int newValue) {
    unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
}

//[...]

使用Unsafe 仍然是一种选择(虽然我认为获取实例有点棘手),但不鼓励这样做,因为你必须自己检查数组边界,如果你犯了错误,它可能会导致 Java 进程出现段错误,而VarHandle 会为您进行边界检查并在给定索引超出范围时引发 Java 异常(但这可能会带来性能成本)。除此之外,Unsafe 不受官方支持,可能随时被删除。

但从 JDK 10 开始,AtomicInteger 中仍然使用 AtomicInteger,因为 '未解决的循环启动依赖关系'


如果您想了解更多有关可用的不同 get 和 set 方法的信息,请查看 Using JDK 9 Memory Order Modes(我不得不说我根本不是这方面的专家(还没有?))。


请注意,从今天开始,您不能在 Kotlin 中使用 VarHandle,因为它将 get 和 set 方法的可变参数 Object... 包装在 Object[] 中,请参阅 bug KT-26165

java.lang.invoke.WrongMethodTypeException: cannot convert MethodHandle(VarHandle,byte[],int,byte)void to (VarHandle,Object[])void

(现在应该修复)

【讨论】:

    【解决方案4】:

    这个怎么样:

    static class Cell<T> {
            volatile T elem;
        }
    
    private Cell<T>[] alloc(int size){
            Cell<T>[] cells = (Cell<T>[]) (new Cell[size]);
            return cells;
        }
    
     volatile Cell<T>[] arr;
     Cell<T>[] newarr = alloc(16);
     for (int i = 0; i < newarr.length; i++) {
          newarr[i] = new Cell<>();
     }
     arr = newarr;
    

    单元格也会使内容不稳定。我也仅在预先分配单元后才将新数组分配给易失性数组...存在 Cell 额外内存的权衡,但它是可管理的

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-22
      • 1970-01-01
      • 1970-01-01
      • 2018-12-17
      • 1970-01-01
      相关资源
      最近更新 更多