Подтвердить что ты не робот

Как объявить элементы массива volatile в Java?

Есть ли способ объявить элементы массива volatile в Java? То есть.

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

объявляет ссылку массива volatile, но элементы массива (например, a[1]) по-прежнему нестабильны. Поэтому я ищу что-то вроде

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

но это не работает. Возможно ли вообще?

4b9b3361

Ответ 1

Используйте AtomicIntegerArray или AtomicLongArray или AtomicReferenceArray

Класс AtomicIntegerArray реализует массив int, чьи отдельные поля могут быть доступны с изменчивой семантикой с помощью методов класса get() и set(). Вызов arr.set(x, y) из одного потока будет гарантировать, что другой поток, вызывающий arr.get(x), будет считывать значение y (пока другое значение не будет прочитано в положение x).

См:

Ответ 3

Еще один способ сделать это - использовать класс JDK 9+ VarHandle. Как вы можете видеть в исходном коде классов Atomic xxx Array, таких как 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.

И затем вы можете получить к нему доступ, используя методы set xxx (array, index, value) и get xxx (array, index), где array имеет тип yourArrayClass, index имеет тип int, value является типа элемента в вашем массиве (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 Unsafe все еще используется в AtomicInteger из-за "неразрешенных циклических зависимостей запуска".


Если вы хотите узнать больше о различных доступных методах get и set, посмотрите Использование режимов упорядочения памяти JDK 9 (я должен сказать, что я совсем не эксперт в этом (пока? )).


Обратите внимание, что на сегодняшний день вы не можете использовать VarHandle в Kotlin, потому что он упаковывает параметры vararg Object... методов get и set в Object[], см. ошибка 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;

ячейки делают содержание также изменчивым. также я назначаю новый массив энергозависимому только после предварительного выделения ячеек... это компромисс дополнительной памяти ячейки, но он управляем