Когда использовать AtomicReference в Java?

Когда мы используем AtomicReference?

Нужно ли создавать объекты во всех многопоточных программах?

Предоставьте простой пример использования AtomicReference.

Ответ 1

Атомная ссылка должна использоваться в настройке, где вам нужно выполнить простые атомарные (то есть, потокобезопасные, нетривиальные) операции над ссылкой, для которых синхронизация на основе монитора не подходит. Предположим, вы хотите проверить, есть ли конкретное поле только в том случае, если состояние объекта остается последним при проверке:

AtomicReference<Object> cache = new AtomicReference<Object>();

Object cachedValue = new Object();
cache.set(cachedValue);

//... time passes ...
Object cachedValueToUpdate = cache.get();
//... do some work to transform cachedValueToUpdate into a new version
Object newValue = someFunctionOfOld(cachedValueToUpdate);
boolean success = cache.compareAndSet(cachedValue,cachedValueToUpdate);

Из-за атомной ссылочной семантики вы можете сделать это, даже если объект cache является общим для потоков, не используя synchronized. В общем, вам лучше использовать синхронизаторы или структуру java.util.concurrent, а не голые Atomic*, если вы не знаете, что делаете.

Две отличные ссылки на мертвые деревья, которые познакомят вас с этой темой: Herlihy превосходное искусство многопроцессорного программирования и Java Concurrency на практике.

Обратите внимание, что (я не знаю, всегда ли это было правдой) назначение ссылок (т.е. =) само является атомом (обновление примитивных 64-битных типов (long/double) может не быть атомарным, но обновление ссылка всегда атомарна, даже если она 64-разрядная) без явного использования Atomic*. См. JLS 3ed, раздел 17.7,

Ответ 2

Атомная ссылка идеальна для использования, когда вам нужно делиться и изменять состояние неизменяемого объекта между несколькими потоками. Это суперплотное утверждение, поэтому я немного сломаю его.

Во-первых, неизменяемый объект - это объект, который после строительства не изменяется. Часто методы неизменяемых объектов возвращают новые экземпляры того же класса. Некоторые примеры включают классы-оболочки Long и Double, а также String, чтобы назвать несколько. (Согласно Программе Concurrency на неизменяемых объектах JVM есть критическая часть современного concurrency).

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

volatile String sharedValue;
static final Object lock=new Object();
void modifyString(){
  synchronized(lock){
    sharedValue=sharedValue+"something to add";
  }
}

Каждый раз, когда вы хотите изменить строку, на которую ссылается это изменчивое поле, на основе его текущего значения, вам сначала нужно получить блокировку этого объекта. Это предотвращает переход некоторых других потоков за это время и изменение значения в середине новой конкатенации строк. Затем, когда ваш поток возобновляется, вы clobber работу другого потока. Но, честно говоря, этот код будет работать, он выглядит чистым, и это сделает большинство людей счастливыми.

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

Другой вариант - использовать AtomicRefrence.

public static AtomicReference<String> shared = new AtomicReference<>();
String init="Inital Value";
shared.set(init);
//now we will modify that value
boolean success=false;
while(!success){
  String prevValue=shared.get();
  // do all the work you need to
  String newValue=shared.get()+"lets add something";
  // Compare and set
  success=shared.compareAndSet(prevValue,newValue);
}

Теперь почему это лучше? Честно говоря, этот код немного менее чист, чем раньше. Но есть что-то действительно важное, что происходит под капотом в AtomicRefrence, и это сравнение и своп. Это одна команда процессора, а не вызов ОС, что делает коммутатор. Это одна инструкция по процессору. И поскольку нет замков, нет контекстного переключателя в случае, когда блокировка активизируется, что экономит еще больше времени!

Ловушка для AtomicReferences не использует вызов .equals(), а вместо сравнения == для ожидаемого значения. Поэтому убедитесь, что ожидаемый объект возвращается из цикла.

Ответ 3

Вот пример использования AtomicReference:

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

public class NumberRange {
    // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

    public void setLower(int i) {
        // Warning -- unsafe check-then-act
        if (i > upper.get())
            throw new IllegalArgumentException(
                    "can't set lower to " + i + " > upper");
        lower.set(i);
    }

    public void setUpper(int i) {
        // Warning -- unsafe check-then-act
        if (i < lower.get())
            throw new IllegalArgumentException(
                    "can't set upper to " + i + " < lower");
        upper.set(i);
    }

    public boolean isInRange(int i) {
        return (i >= lower.get() && i <= upper.get());
    }
}

Оба setLower и setUpper являются последовательностями check-then-act, но они не используют достаточную блокировку, чтобы сделать их атомарными. Если диапазон номеров удерживается (0, 10), а один поток вызывает setLower (5), а другой поток вызывает setUpper (4), с некоторым неудачным временем оба будут проходить проверки в сеттерах, и будут применены обе модификации. В результате диапазон теперь удерживает (5, 4) недопустимое состояние. Поэтому, в то время как базовые AtomicIntegers являются потокобезопасными, составной класс не является. Это можно устранить, используя AtomicReference вместо использования отдельных AtomicIntegers для верхней и нижней границ.

public class CasNumberRange {
    //Immutable
    private static class IntPair {
        final int lower;  // Invariant: lower <= upper
        final int upper;
        ...
    }
    private final AtomicReference<IntPair> values =
        new AtomicReference<IntPair>(new IntPair(0, 0));

    public int getLower() { return values.get().lower; }
    public int getUpper() { return values.get().upper; }

    public void setLower(int i) {
        while (true) {
            IntPair oldv = values.get();
            if (i > oldv.upper)
                throw new IllegalArgumentException(
                   "Can't set lower to " + i + " > upper");
            IntPair newv = new IntPair(i, oldv.upper);
            if (values.compareAndSet(oldv, newv))
                return;
        }
    }
    // similarly for setUpper
}

Ответ 4

Вы можете использовать AtomicReference при применении оптимистичных блокировок. У вас есть общий объект, и вы хотите изменить его из более чем 1 потока.

  • Вы можете создать копию общего объекта
  • Изменить общий объект
  • Вам нужно проверить, что общий объект по-прежнему совпадает с предыдущим - если да, то обновите ссылку с измененной копией.

Как и другой поток, возможно, он изменил его и/может изменить между этими двумя шагами. Вам нужно сделать это в атомной операции. здесь AtomicReference может помочь

Ответ 5

Еще один простой пример - сделать модификацию безопасного потока в объекте сеанса.

public PlayerScore getHighScore() {
    ServletContext ctx = getServletConfig().getServletContext();
    AtomicReference<PlayerScore> holder 
        = (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
    return holder.get();
}

public void updateHighScore(PlayerScore newScore) {
    ServletContext ctx = getServletConfig().getServletContext();
    AtomicReference<PlayerScore> holder 
        = (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
    while (true) {
        HighScore old = holder.get();
        if (old.score >= newScore.score)
            break;
        else if (holder.compareAndSet(old, newScore))
            break;
    } 
}

Источник: http://www.ibm.com/developerworks/library/j-jtp09238/index.html

Ответ 6

Когда мы используем AtomicReference?

AtomicReference - это гибкий способ атомарного обновления значения переменной без использования синхронизации.

AtomicReference поддерживает незакрепленное потокобезопасное программирование для отдельных переменных.

Существует несколько способов обеспечения безопасности Thread, используя высокопроизводительный параллельный API на странице документации оракула. Использование атомных переменных является одним из нескольких вариантов.

Lock объекты поддерживают блокировки идиом, которые упрощают многие параллельные приложения.

Executors определяют API высокого уровня для запуска и управления потоками. Реализации Executor, предоставленные java.util.concurrent, обеспечивают управление пулами потоков, подходящее для крупномасштабных приложений.

Concurrent collections упрощает управление большими наборами данных и может значительно уменьшить необходимость синхронизации.

Atomic variables имеют функции, которые минимизируют синхронизацию и помогают избежать ошибок согласованности памяти.

Предоставьте простой пример использования AtomicReference.

Пример кода с AtomicReference:

String initialReference = "value 1";

AtomicReference<String> someRef =
    new AtomicReference<String>(initialReference);

String newReference = "value 2";
boolean exchanged = someRef.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);

Нужно ли создавать объекты во всех многопоточных программах?

Вам не нужно использовать AtomicReference во всех многопоточных программах.

Если вы хотите защитить одну переменную, используйте AtomicReference. Если вы хотите защитить блок кода, используйте другие конструкции, такие как Lock/synchronized и т.д.