Когда мы используем AtomicReference
?
Нужно ли создавать объекты во всех многопоточных программах?
Приведите простой пример использования AtomicReference.
Когда мы используем AtomicReference
?
Нужно ли создавать объекты во всех многопоточных программах?
Приведите простой пример использования AtomicReference.
Ответы:
Атомная ссылка должна использоваться в настройках, где вам нужно выполнять простые атомарные (т. Е. Поточно- ориентированные, нетривиальные) операции над ссылкой, для которых синхронизация на основе монитора не подходит. Предположим, вы хотите проверить, является ли определенное поле только в том случае, если состояние объекта остается таким, как вы в последний раз проверяли
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*
если вы не знаете, что делаете.
Две отличные ссылки о мертвом дереве, которые познакомят вас с этой темой:
Обратите внимание, что (я не знаю, было ли это всегда так), присвоение ссылки (то есть =
) само по себе является атомарным (обновление примитивных 64-битных типов, таких как long
или double
может не быть атомарным, но обновление ссылки всегда атомарно, даже если оно 64-битное ) без явного использования Atomic*
.
См. Спецификацию языка Java 3ed, Раздел 17.7 .
AtomicReference
вы должны пометить переменную, volatile
потому что, хотя среда выполнения гарантирует, что присвоение ссылки является атомарным, компилятор может выполнять оптимизацию в предположении, что переменная не изменялась другими потоками.
AtomicReference
"; если вы будете использовать его, то мой совет будет идти в направлении , противоположного и пометить его , final
так что компилятор может оптимизировать соответственно.
Атомная ссылка идеальна для использования, когда вам нужно совместно использовать и изменять состояние неизменяемого объекта между несколькими потоками. Это очень плотное утверждение, поэтому я его немного разобью.
Во-первых, неизменный объект - это объект, который практически не изменяется после построения. Часто методы неизменяемого объекта возвращают новые экземпляры этого же класса. Некоторые примеры включают классы-обёртки Long и Double, а также String, и это лишь некоторые из них. (Согласно данным Programm Concurrency на 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.
worked
чтобы получить ту же семантику.
Вот пример использования для 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 IntPair(int lower, int upper) {
this.lower = lower;
this.upper = upper;
}
}
private final AtomicReference<IntPair> values =
new AtomicReference<IntPair>(new IntPair(0, 0));
public int getLower() {
return values.get().lower;
}
public void setLower(int lower) {
while (true) {
IntPair oldv = values.get();
if (lower > oldv.upper)
throw new IllegalArgumentException(
"Can't set lower to " + lower + " > upper");
IntPair newv = new IntPair(lower, oldv.upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
public int getUpper() {
return values.get().upper;
}
public void setUpper(int upper) {
while (true) {
IntPair oldv = values.get();
if (upper < oldv.lower)
throw new IllegalArgumentException(
"Can't set upper to " + upper + " < lower");
IntPair newv = new IntPair(oldv.lower, upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
}
Вы можете использовать AtomicReference при применении оптимистических блокировок. У вас есть общий объект, и вы хотите изменить его из более чем одного потока.
Поскольку другой поток мог изменить его и / может изменить между этими 2 шагами. Вы должны сделать это в атомарной операции. это где AtomicReference может помочь
Вот очень простой пример использования, который не имеет ничего общего с безопасностью потоков.
Чтобы разделить объект между лямбда-вызовами, AtomicReference
есть опция :
public void doSomethingUsingLambdas() {
AtomicReference<YourObject> yourObjectRef = new AtomicReference<>();
soSomethingThatTakesALambda(() -> {
yourObjectRef.set(youObject);
});
soSomethingElseThatTakesALambda(() -> {
YourObject yourObject = yourObjectRef.get();
});
}
Я не говорю, что это хороший дизайн или что-то в этом роде (это просто тривиальный пример), но если у вас есть случай, когда вам нужно разделить объект между лямбда-вызовами, AtomicReference
опция является опцией.
Фактически вы можете использовать любой объект, который содержит ссылку, даже коллекцию, которая имеет только один элемент. Тем не менее, AtomicReference идеально подходит.
Я не буду много говорить. Мои уважаемые коллеги уже внесли свой ценный вклад.Полноценный исполняемый код в конце этого блога должен устранить любую путаницу. Речь идет о небольшой программе бронирования мест в кино по многопоточному сценарию.
Некоторые важные элементарные факты заключаются в следующем. 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
}
}
}
}
Когда мы используем 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
и т. Д.
Другой простой пример - модификация безопасного потока в объекте сеанса.
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