Перейти к основному содержимому

Различия между set() и lazySet() в атомарных переменных Java

· 4 мин. чтения

1. Обзор

В этом руководстве мы рассмотрим различия между методами set() и lazySet() атомарных классов Java , таких как AtomicInteger и AtomicReference .

2. Атомарные переменные — краткий обзор

Атомарные переменные в Java позволяют нам легко выполнять потокобезопасные операции со ссылками на классы или полями без необходимости добавлять примитивы параллелизма, такие как мониторы или мьютексы.

Они определены в пакете java.util.concurrent.atomic , и хотя их API различаются в зависимости от атомарного типа, большинство из них поддерживают методы set() и lazySet() .

Для простоты в этой статье мы будем использовать AtomicReference и AtomicInteger , но те же принципы применимы и к другим атомарным типам.

3. Метод set ()

Метод set() эквивалентен записи в volatile поле .

После вызова set(), когда мы получаем доступ к полю с помощью метода get() из другого потока, изменение сразу становится видимым. Это означает, что значение было сброшено из кэша ЦП в уровень памяти, общий для всех ядер ЦП.

Чтобы продемонстрировать вышеуказанную функциональность, давайте создадим минимальное консольное приложение производитель-потребитель :

public class Application {

AtomicInteger atomic = new AtomicInteger(0);

public static void main(String[] args) {
Application app = new Application();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
app.atomic.set(i);
System.out.println("Set: " + i);
Thread.sleep(100);
}
}).start();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
synchronized (app.atomic) {
int counter = app.atomic.get();
System.out.println("Get: " + counter);
}
Thread.sleep(100);
}
}).start();
}
}

В консоли мы должны увидеть серию сообщений «Set» и «Get»:

Set: 3
Set: 4
Get: 4
Get: 5

Что указывает на когерентность кэша , так это тот факт, что значения в операторах «Get» всегда равны или больше, чем значения в операторах «Set» над ними.

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

4. Метод lazySet ()

Метод lazySet() аналогичен методу set() , но без сброса кеша.

Другими словами, наши изменения становятся видимыми только для других потоков . Это означает, что вызов get() для обновленной AtomicReference из другого потока может дать нам старое значение.

Чтобы увидеть это в действии, давайте изменим Runnable первого потока в нашем предыдущем консольном приложении:

for (int i = 0; i < 10; i++) {
app.atomic.lazySet(i);
System.out.println("Set: " + i);
Thread.sleep(100);
}

Новые сообщения «Set» и «Get» могут не всегда увеличиваться:

Set: 4
Set: 5
Get: 4
Get: 5

Из-за характера потоков нам может потребоваться несколько повторных запусков приложения, чтобы вызвать такое поведение. Тот факт, что поток-потребитель сначала извлекает значение 4, несмотря на то, что поток-производитель установил для AtomicInteger значение 5, означает, что система в конечном счете непротиворечива при использовании lazySet() .

В более технических терминах мы говорим, что методы lazySet() не действуют в коде так, как это происходит перед ребрами, в отличие от их аналогов set() .

5. Когда использовать lazySet()

Не сразу ясно, когда мы должны использовать lazySet(), поскольку его отличия от set() незначительны. Нам нужно тщательно проанализировать проблему не только для того, чтобы убедиться, что мы получим прирост производительности, но и для обеспечения корректности в многопоточной среде.

Один из способов его использования — заменить ссылку на объект на null , как только он нам больше не понадобится. Таким образом, мы указываем, что объект подходит для сборки мусора без каких-либо потерь производительности. Мы предполагаем, что другие потоки могут работать с устаревшим значением, пока не увидят, что AtomicReference имеет значение null .

Однако, как правило, мы должны использовать lazySet() , когда хотим внести изменение в атомарную переменную, и мы знаем, что изменение не обязательно должно быть немедленно видно другим потокам.

6. Заключение

В этой статье мы рассмотрели различия между методами set() и lazySet() атомарных классов. Мы также узнали, когда какой метод использовать.

Как всегда, исходный код примеров можно найти на GitHub .