Статьи

Добавление @atomic Operations в Java


Как могут работать атомарные операции в Java, и есть ли текущая альтернатива в OpenJDK / Hotspot, на которую она может быть переведена.

Обратная связь

В моей предыдущей статье о том, как  сделать операции над летучими полями атомарными.  Несколько раз отмечалось, что «исправление» предыдущего поведения вряд ли будет продолжаться независимо от благих намерений.

Альтернативой этому является добавление анатомии @atomic. Это имеет преимущество только в применении к новому коду и не рискует сломать старый код.


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

Атомные операции


Любое поле, указанное с помощью @atomic, сделает все выражение атомарным.
Переменные, которые являются энергонезависимыми и неатомарными, могут быть прочитаны в начале или установлены после завершения выражения. Само выражение может потребовать блокировки на некоторых платформах, операций CAS или TSX в зависимости от технологии ЦП.

Если поля только для чтения или написано только одно, это будет то же самое, что и volatile.

Булево атомное


В настоящее время AtomicBoolean использует 4 байта плюс заголовок объекта с возможным заполнением (а также ссылкой). Если поле было встроено, оно могло бы выглядеть следующим образом
@atomic boolean flag;
// toggle the flag.
this.flag = !this.flag;

Но как это будет работать?
Не все платформы поддерживают 1-байтовые атомарные операции, например, Unsafe имеет 1-байтовые операции CAS. Это можно сделать с помощью маскировки.
// possible replacement.
while(true) {
     int num = Unsafe.getUnsafe().getVolatileInt(this, FLAG_OFFSET & ~3); // word align the access.
     int value ^= 1 << ~(0xFF << (FLAG_OFFSET & 3) * 8) ;
     if (Unsafe.getUnsafe().compareAndSwapInt(this, FLAG_OFFSET & ~3, num, value))
           break;
}
Атомный двухместный


Тип, который не поддерживается, это AtomicDouble, но это вариант AtomicLong.
Рассмотрим этот пример.
@atomic double a = 1;
volatile double b = 2;
a += b;
Как это может быть реализовано сегодня?

while(true) {
    double _b = Unsafe.getUnsafe().getVolatileDouble(this, B_OFFSET);
    double _a = Unsafe.getUnsafe().getVolatileDouble(this, A_OFFSET);
    long aAsLong = Double.doubleToRawLongBits(_a);
    double _sum = _a + _b;
    long sumAsLong = Double.doubleToRawLongBits(_a);
    if (Unsafe.getUnsafe().compareAndSwapLong(this, A_OFFSET, aAsLong, sumAsLong))
        break;
}

Два атомных поля


Используя Intel TSX, вы можете обернуть аппаратную транзакцию вокруг нескольких полей, но что, если у вас нет TSX, все равно это можно сделать, не прибегая к блокировке.
@atomic int a = 1, b = 2;
a += b * (b % 2 == 0 ? 2 : 1);

Это все еще можно сделать с помощью CAS, если поля вместе.
Запланирована операция CAS2, чтобы иметь возможность проверить два 64-битных значения. Пока этот пример будет использовать два 4-байтовых значения.
assert A_OFFSET + 4 == B_OFFSET;
while(true) {
    long _ab = Unsafe.getUnsafe().getVolatileLong(this, A_OFFSET);
    int _a = getLowerInt(_ab);
    int _b = getHigherInt(_ab);
    int _sum = _a + _b * (_b % 2 == 0 ? 2 : 1);
    int _sum_ab = setLowerIntFor(_ab, _sum);
    if (Unsafe.getUnsafe().compareAndSwapLong(this, A_OFFSET, _ab, _sum_ab))
        break;
}

Примечание. Эта операция может обрабатывать изменение либо a, либо b, либо обоих элементов атомарным способом.

AtomicReferences


Операции общего случая использования с неизменяемыми объектами, такими как BigDecimal.
@atomic BigDecimal a;
BigDecimal b;
a = a.add(b);
может быть реализован таким образом в системах с CompressedOops или 32-битных JVM.

BigDecimal _b = this.b;
while(true) {
    BigDecimal _a = (BigDecimal) Unsafe.getUnsafe().getVolatileObject(this, A_OFFSET);
    BigDecimal _sum = _a.add(_b);
    if (Unsafe.getUnsafe().compareAndSwapLong(this, A_OFFSET, _a, _sum))
        break;
}

Более сложные примеры


Всегда будут примеры, которые слишком сложны для вашей платформы.
Они могут подойти для Системы с системами, поддерживаемыми TSX или HotSpot, однако вам нужно отступить.
@atomic long a, b, c, d;
a = (b = (c = d + 4) + 5 ) + 6;
В настоящее время это не поддерживается, поскольку в одном выражении задано несколько длинных значений. Однако отступление может заключаться в использовании существующей блокировки.

synchronized(this) {
    a = (b = (c = d + 4) + 5 ) + 6;
}

Вывод


Добавив аннотацию, мы могли бы добавить элементарные операции к обычным полям без необходимости изменять синтаксис.
Это было бы естественным расширением языка без нарушения обратной сопоставимости.