Статьи

Выполнение операций на летучих атомных полях

обзор

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

Решение в Java 5.0+ состоит в том, чтобы использовать классы AtomicXxxx, однако они относительно неэффективны с точки зрения памяти (они добавляют заголовок и заполнение), производительности (они добавляют ссылки и небольшой контроль над их относительными позициями), и синтаксически они не так ясно, чтобы использовать.

ИМХО Простое решение, если изменчивые поля будут действовать так, как они могут ожидать, способ, которым JVM должна поддерживать в AtomicFields, что не запрещено в текущей JMM (Java-Memory Model), но не гарантируется.

Зачем делать поля нестабильными ?

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

например, без летучих

1
2
3
Thread 2:  int a = 5;
 
Thread 1:  a = 6;

(позже)

1
Thread 2: System.out.println(a); // prints 5 or 6

С летучим

1
2
3
Thread 2:  volatile int a = 5;
 
Thread 1: a = 6;

(позже)

1
Thread 2: System.out.println(a); // prints 6 given enough time.

Почему бы не использовать volatile все время?

Изменчивый доступ для чтения и записи значительно медленнее. Когда вы записываете в энергозависимое поле, оно останавливает весь конвейер ЦП, чтобы гарантировать, что данные были записаны в кэш. Без этого существует риск, что при следующем чтении значения будет обнаружено старое значение, даже в том же потоке (см. AtomicLong.lazySet (), который предотвращает остановку конвейера)

Наказание может быть в 10 раз медленнее, что вы не хотите делать при каждом доступе.

Каковы ограничения изменчивости ?

Существенным ограничением является то, что операции на поле не являются атомарными, даже если вы думаете, что это так. Еще хуже то, что обычно нет никакой разницы. Т.е. он может работать долгое время, даже годы, и внезапно / случайно сломаться из-за случайного изменения, такого как используемая версия Java или даже когда объект загружается в память. например, какие программы вы загрузили перед запуском программы.

например, обновление значения

1
2
3
4
Thread 2:  volatile int a = 5;
 
Thread 1:  a += 1;
Thread 2:  a += 2;

(позже)

1
Thread 2: System.out.println(a); // prints 6, 7 or 8 even given enough time.

Это проблема, потому что чтение a и запись a выполняются отдельно, и вы можете получить условие гонки. 99% + времени он будет вести себя как ожидалось, но иногда это не так.

Что вы можете с этим поделать?

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

1
2
3
4
Thread 2:  AtomicInteger a = new AtomicInteger(5);
 
Thread 1:  a.incrementAndGet();
Thread 2:  a.addAndGet(2);

(позже)

1
Thread 2: System.out.println(a); // prints 8 given enough time.

Что я предлагаю?

У JVM есть средства для того, чтобы вести себя как положено, единственное, что удивительно, это то, что вам нужно использовать специальный класс, чтобы делать то, что JMM не гарантирует для вас. Я предлагаю изменить JMM, чтобы он поддерживал поведение, предоставляемое в настоящее время классами параллелизма AtomicClasses.

В каждом случае однопоточное поведение остается неизменным. Многопоточная программа, которая не видит условия гонки, будет вести себя так же. Разница в том, что многопоточная программа не должна видеть состояние гонки, а должна изменять основное поведение.

текущий метод предлагаемый синтаксис заметки
x.getAndIncrement () х ++ или х + = 1
x.incrementAndGet () ++ х
x.getAndDecrment () х — или х — = 1
x.decrementAndGet () -Икс
x.addAndGet (у) (х + = у)
x.getAndAdd (у) ((x + = y) -y)
x.compareAndSet (e, y) (x == e? x = y, true: false) Необходимо добавить синтаксис запятой
используется на других языках.

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

Могут поддерживаться дополнительные операторы присваивания, такие как:

текущий метод предлагаемый синтаксис заметки
Атомное умножение х * = 2;
Атомное вычитание х — = у;
Атомный отдел х / = у;
Атомный модуль х% = у;
Атомный сдвиг х << = у;
Атомный сдвиг x >> = z;
Атомный сдвиг х >>> = ш;
Атомный и х & = ~ у; очищает биты
Атомный или x | = z; устанавливает биты
Атомный XOR х ^ = ш; переворачивает биты

Какой риск?

Это может привести к повреждению кода, который иногда приводит к сбою этих операций из-за условий гонки.

Может быть невозможным поддерживать более сложные выражения потокобезопасным способом. Это может привести к неожиданным ошибкам, так как код может выглядеть как работа, но это не так. Тем не менее, оно будет не хуже нынешнего состояния.

JEP 193 — Улучшенные летучие вещества

Существует JEP 193 для добавления этой функциональности в Java. Примером является:

1
2
3
4
5
6
class Usage {
    volatile int count;
    int incrementCount() {
        return count.volatile.incrementAndGet();
    }
}

ИМХО есть несколько ограничений в этом подходе.

  • Синтаксис довольно значительного изменения. Для изменения JMM может не потребоваться много изменений синтаксиса Java и, возможно, никаких изменений в компиляторе.
  • Это менее общее решение. Это может быть полезно для поддержки таких операций, как объем + = количество; где это двойные типы.
  • Разработчику ложится бремя понимания, почему он / она должен использовать это вместо x ++ ;

Я не уверен, что более громоздкий синтаксис проясняет, что происходит. Рассмотрим этот пример:

1
2
3
volatile int a, b;
 
a += b;

или же

1
a.volatile.addAndGet(b.volatile);

или же

1
2
3
AtomicInteger a, b;
 
a.addAndGet(b.get());

Какие из этих операций, как строки, являются атомарными. Ни на один из них не отвечайте, однако системы с Intel TSX могут сделать их атомарными, и если вы собираетесь изменить поведение любой из этих строк кода, я бы сделал a + = b; вместо того, чтобы изобретать новый синтаксис, который делает то же самое большую часть времени, но один гарантирован, а другой нет.

Вывод

Большая часть синтаксических и производственных издержек использования AtomicInteger и AtomicLong может быть удалена, если JMM гарантирует, что эквивалентные однопоточные операции ведут себя, как и ожидалось, для многопоточного кода.

Эта функция может быть добавлена ​​к более ранним версиям Java с помощью инструментария байтового кода.

Ссылка: Делать операции над изменчивыми полями атомарными от нашего партнера JCG Питера Лоури из блога Vanilla Java .