обзор
Как могут работать атомарные операции в 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 . |