Когда использовать AtomicReference в Java? - PullRequest
270 голосов
/ 19 октября 2010

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

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

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

Ответы [ 8 ]

186 голосов
/ 19 октября 2010

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

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 framework, а не просто Atomic*, если вы не знаете, что делаете.

Две отличные ссылки о мертвом дереве, которые познакомят вас с этой темой:

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

79 голосов
/ 15 июля 2014

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

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

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

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

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

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

Другой вариант - использовать 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 (), а вместо этого == сравнение ожидаемого значения.Поэтому убедитесь, что ожидаемый является фактическим объектом, возвращаемым из get в цикле.

26 голосов
/ 29 мая 2015

Вот пример использования для 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 являются последовательностями "проверяй и действуй", но они не используют достаточную блокировку, чтобы сделать их атомарными. Если диапазон номеров содержит (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
}
19 голосов
/ 12 июня 2015

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

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

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

6 голосов
/ 31 мая 2018

Я не буду много говорить. Мои уважаемые коллеги уже внесли свой ценный вклад. Полноценный исполняемый код в конце этого блога должен устранить любую путаницу. Речь идет о небольшой программе бронирования мест в кино в многопоточном сценарии.

Некоторые важные элементарные факты заключаются в следующем. 1> Различные потоки могут бороться только за экземплярные и статические переменные-члены в пространстве кучи. 2> Volatile чтение или запись полностью атомарны и сериализуются / происходят раньше и делаются только из памяти. Говоря это, я имею в виду, что любое чтение будет следовать за предыдущей записью в памяти. И любая запись будет следовать за предыдущим чтением из памяти. Таким образом, любой поток, работающий с volatile, всегда будет видеть самое актуальное значение. AtomicReference использует это свойство volatile.

Ниже приводится часть исходного кода AtomicReference. AtomicReference ссылается на ссылку на объект. Эта ссылка является переменной типа volatile в экземпляре AtomicReference, как показано ниже.

private volatile V value;

get () просто возвращает последнее значение переменной (как это происходит с волатильным способом «происходит раньше»).

public final V get()

Ниже приводится наиболее важный метод AtomicReference.

public final boolean  compareAndSet(V expect, V update) {
        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

Метод compareAndSet (ожидание, обновление) вызывает метод compareAndSwapObject () небезопасного класса Java. Этот вызов метода unsafe вызывает собственный вызов, который вызывает единственную инструкцию для процессора. «ожидайте» и «обновляйте» каждая ссылка на объект.

В том и только в том случае, если переменная-член экземпляра AtomicReference «значение» ссылается на тот же объект, на который ссылается «ожидание», «переменная» теперь назначается этой переменной экземпляра, и возвращается «истина». Или же ложь возвращается. Все это сделано атомарно. Никакой другой поток не может перехватить между ними. Поскольку это однопроцессорная операция (магия современной компьютерной архитектуры), она часто быстрее, чем использование синхронизированного блока. Но помните, что, когда несколько переменных необходимо обновить атомарно, AtomicReference не поможет.

Я бы хотел добавить полноценный работающий код, который можно запускать в Eclipse. Было бы ясно, много путаницы. Здесь 22 пользователя (темы MyTh) пытаются забронировать 20 мест. Ниже приведен фрагмент кода, за которым следует полный код.

Фрагмент кода, где 22 пользователя пытаются забронировать 20 мест.

for (int i = 0; i < 20; i++) {// 20 seats
            seats.add(new AtomicReference<Integer>());
        }
        Thread[] ths = new Thread[22];// 22 users
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new MyTh(seats, i);
            ths[i].start();
        }

Ниже приводится полный рабочий код.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public class Solution {

    static List<AtomicReference<Integer>> seats;// Movie seats numbered as per
                                                // list index

    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        seats = new ArrayList<>();
        for (int i = 0; i < 20; i++) {// 20 seats
            seats.add(new AtomicReference<Integer>());
        }
        Thread[] ths = new Thread[22];// 22 users
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new MyTh(seats, i);
            ths[i].start();
        }
        for (Thread t : ths) {
            t.join();
        }
        for (AtomicReference<Integer> seat : seats) {
            System.out.print(" " + seat.get());
        }
    }

    /**
     * id is the id of the user
     * 
     * @author sankbane
     *
     */
    static class MyTh extends Thread {// each thread is a user
        static AtomicInteger full = new AtomicInteger(0);
        List<AtomicReference<Integer>> l;//seats
        int id;//id of the users
        int seats;

        public MyTh(List<AtomicReference<Integer>> list, int userId) {
            l = list;
            this.id = userId;
            seats = list.size();
        }

        @Override
        public void run() {
            boolean reserved = false;
            try {
                while (!reserved && full.get() < seats) {
                    Thread.sleep(50);
                    int r = ThreadLocalRandom.current().nextInt(0, seats);// excludes
                                                                            // seats
                                                                            //
                    AtomicReference<Integer> el = l.get(r);
                    reserved = el.compareAndSet(null, id);// null means no user
                                                            // has reserved this
                                                            // seat
                    if (reserved)
                        full.getAndIncrement();
                }
                if (!reserved && full.get() == seats)
                    System.out.println("user " + id + " did not get a seat");
            } catch (InterruptedException ie) {
                // log it
            }
        }
    }

}    
5 голосов
/ 23 апреля 2018

Вот очень простой пример использования, который не имеет ничего общего с безопасностью потоков.

Чтобы разделить объект между лямбда-вызовами, AtomicReference - это опция :

public void doSomethingUsingLambdas() {

    AtomicReference<YourObject> yourObjectRef = new AtomicReference<>();

    soSomethingThatTakesALambda(() -> {
        yourObjectRef.set(youObject);
    });

    soSomethingElseThatTakesALambda(() -> {
        YourObject yourObject = yourObjectRef.get();
    });
}

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

Фактически вы можете использовать любой объект, который содержит ссылку,даже коллекция, которая имеет только один предмет.Тем не менее, AtomicReference идеально подходит.

2 голосов
/ 20 мая 2016

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

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

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

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

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

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

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

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

Приведите простой пример использования 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 и т. Д.

0 голосов
/ 15 июля 2015

Другой простой пример - модификация безопасного потока в объекте сеанса.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...