Статьи

Добавление @atomic операций в Java

обзор

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

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

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

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

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

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

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

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

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

В настоящее время AtomicBoolean использует 4 байта плюс заголовок объекта с возможным заполнением (а также ссылкой). Если поле было встроено, оно могло бы выглядеть следующим образом

1
2
3
@atomic boolean flag;
// toggle the flag.
this.flag = !this.flag;

Но как это будет работать? Не все платформы поддерживают 1-байтовые атомарные операции, например, Unsafe имеет 1-байтовые операции CAS Это можно сделать с помощью маскировки.

1
2
3
4
5
6
7
// 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. Рассмотрим этот пример.

1
2
3
4
@atomic double a = 1;
volatile double b = 2;
 
a += b;

Как это может быть реализовано сегодня?

1
2
3
4
5
6
7
8
9
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, все равно это можно сделать, не прибегая к блокировке.

1
2
3
@atomic int a = 1, b = 2;
 
a += b * (b % 2 == 0 ? 2 : 1);

Это все еще можно сделать с помощью CAS, если поля вместе. Запланирована операция CAS2, чтобы иметь возможность проверить два 64-битных значения. Пока этот пример будет использовать два 4-байтовых значения.

01
02
03
04
05
06
07
08
09
10
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.

1
2
3
4
@atomic BigDecimal a;
BigDecimal b;
 
a = a.add(b);

может быть реализован таким образом в системах с CompressedOops или 32-битных JVM.

1
2
3
4
5
6
7
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, однако вам нужно отступить.

1
2
3
@atomic long a, b, c, d;
 
a = (b = (c = d + 4) +  5 ) + 6;

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

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

Вывод

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

Ссылка: Добавление @atomic операций к Java от нашего партнера JCG Питера Лоури из блога Vanilla Java .