如何在Java中声明数组元素为易失性?

发布于 2024-08-20 21:40:42 字数 289 浏览 5 评论 0原文

有没有办法在Java中声明数组元素易失性?即

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

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

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

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

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

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

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

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

尛丟丟 2024-08-27 21:40:42

使用 AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray

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

请参阅:

Use AtomicIntegerArray or AtomicLongArray or AtomicReferenceArray

The AtomicIntegerArray class 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).

See:

半步萧音过轻尘 2024-08-27 21:40:42

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

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

梦里泪两行 2024-08-27 21:40:42

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

//[...]

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)get 访问它xxx(array, index) 方法,其中 array 的类型为 yourArrayClassindex 的类型为type intvalue 是数组中元素的类型 (yourArrayClass.getComponentType())。

请注意,例如,如果 yourArrayClass == byte[].class 但您输入 42 作为 value,则会收到错误,因为 42 是一个 int 而不是 byte,访问方法的参数是 vararg 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 因为'未解决的循环启动依赖项”。


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


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

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

(现在应该修复)

Another way to do this is using the JDK 9+ VarHandle class. As you can see in the source code of the AtomicxxxArray classes like AtomicIntegerArray, these classes also use VarHandle since 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 VarHandle like this:

MethodHandles.arrayElementVarHandle(yourArrayClass)

For example, you can enter byte[].class here to implement the missing AtomicByteArray yourself.

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

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

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.Unsafe was used to implement the atomic classes like 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 Unsafe is 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 VarHandle does 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, Unsafe is not officially supported and might be removed at any time.

But as of JDK 10 Unsafe is still used in AtomicInteger because of 'unresolved cyclic startup dependencies'.


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?)).


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

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

(should be fixed now)

雨后咖啡店 2024-08-27 21:40:42

怎么样:

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;

细胞也会使内容物变得不稳定。另外,我仅在预分配单元之后才将新数组分配给易失性数组...存在单元额外内存的权衡,但它是可以管理的

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文