如何在 Java 中声明数组元素 volatile?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2236184/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 20:13:17  来源:igfitidea点击:

How to declare array elements volatile in Java?

javaarraysconcurrencyvolatile

提问by Joonas Pulakka

Is there a way to declare array elements volatilein Java? I.e.

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

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

declares the array referencevolatile, but the array elements(e.g. a[1]) are still not volatile. So I'm looking for something like

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

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

but it doesn't work that way. Is it possible at all?

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

采纳答案by uthark

Use AtomicIntegerArrayor AtomicLongArrayor AtomicReferenceArray

使用AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray

The AtomicIntegerArrayclass implements an int array whose individual fields can be accessed with volatile semantics, via the class's get()and set()methods. Calling arr.set(x, y)from one thread will then guarantee that another thread calling arr.get(x)will read the value y (until another value is read to position x).

所述AtomicIntegerArray类实现一个int数组,其单独的字段可与易失性的语义来访问,经由类的get()set()的方法。arr.set(x, y)从一个线程调用将保证另一个线程调用arr.get(x)将读取值 y(直到另一个值被读取到位置 x)。

See:

看:

回答by Tim Jansen

No, you can't make array elements volatile. See also http://jeremymanson.blogspot.com/2009/06/volatile-arrays-in-java.html.

不,你不能让数组元素变得易变。另见http://jeremymanson.blogspot.com/2009/06/volatile-arrays-in-java.html

回答by SWdV

Another way to do this is using the JDK 9+ VarHandleclass. As you can see in the source code of the AtomicxxxArrayclasses like AtomicIntegerArray, these classes also use VarHandlesince JDK 9:

另一种方法是使用 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);
}

//[...]

You first create a VarHandlelike this:

你首先创建一个VarHandle这样的:

MethodHandles.arrayElementVarHandle(yourArrayClass)

For example, you can enter byte[].classhere to implement the missing AtomicByteArrayyourself.

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

And then you can access it using the setxxx(array, index, value)and getxxx(array, index)methods, where arrayis of type yourArrayClass, indexis of type int, valueis of the type of an element in your array (yourArrayClass.getComponentType()).

然后您可以使用setxxx(array, index, value)getxxx(array, index)方法访问它,其中arrayis 是 type yourArrayClassindexis of type intvalue是数组中元素的类型 ( yourArrayClass.getComponentType())。

Note that if, for example, yourArrayClass == byte[].classbut you enter 42as value, you get an error because 42is an intinstead of a byteand the parameters of the access methods are vararg Object...parameters:

请注意,例如,如果yourArrayClass == byte[].class您输入42as value,则会出现错误,因为42is anint而不是 abyte并且访问方法的Object...参数是 vararg参数:

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

(The second signature is the one that you used, the first one is the one that you should have used.)

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



Note that in JDK 8 and below sun.misc.Unsafewas used to implement the atomic classes like AtomicIntegerArray:

请注意,在 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);
}

//[...]

Using Unsafeis still an option (although I think it's a bit tricky to obtain an instance), but it is discouraged because you have to check array bounds yourself and it might segfault the Java process if you make a mistake, while VarHandledoes bounds checks for you and throws a Java exception if the given index is out of bounds (but that may come with a performance cost). Besides that, Unsafeis not officially supported and might be removed at any time.

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

But as of JDK 10 Unsafeis still used in AtomicIntegerbecause of 'unresolved cyclic startup dependencies'.

但是从 JDK 10 开始UnsafeAtomicInteger由于“未解决的循环启动依赖项”,仍在使用中。



If you want to know more about the different get and set methods available, take a look at Using JDK 9 Memory Order Modes(I have to say that I'm not an expert in this at all (yet?)).

如果您想了解有关可用的不同 get 和 set 方法的更多信息,请查看使用 JDK 9 内存顺序模式(我不得不说我根本不是这方面的专家(还?))。



Note that as of today you cannot use VarHandlein Kotlin, because it wraps the vararg Object...parameters of the get and set methods in an Object[], see bug KT-26165:

请注意,截至今天,您无法VarHandle在 Kotlin 中使用,因为它将Object...get 和 set 方法的 vararg参数包装在中Object[],请参阅错误 KT-26165

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

(should be fixed now)

(现在应该修复了)

回答by gotch4

How about this:

这个怎么样:

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;

the cells make the content volatile too. also I assign the new array to the volatile one only after pre allocating the cells... there's the trade off of the Cell extra memory, but it's manageable

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