8 ответов:
атомные ссылка должна использоваться в обстановке, где вам нужно сделать простой atomic (т. е. потокобезопасным, нетривиальные) операции над ссылкой, для которых синхронизация на основе монитора не подходит. Предположим, вы хотите проверить, есть ли определенное поле, только если состояние объекта остается таким же, как и в прошлый раз:
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не может быть атомарным; но обновление a ссылка всегда атомарный, даже если это 64 бит) без явного использованияAtomic*.
Смотрите спецификация языка Java 3ed,17.7.
во-первых, неизменяемый объект-это объект, который фактически не изменяется после построения. Часто методы неизменяемого объекта возвращают новые экземпляры того же класса. Некоторые примеры включают в себя классы-оболочки 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, это не использует a .вызов equals (), но вместо этого сравнение == для ожидаемого значения. Поэтому убедитесь, что ожидаемый-это фактический объект, возвращаемый из get in the loop.
вот пример использования для 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 }
вы можете использовать AtomicReference при применении оптимистических блокировок. У вас есть общий объект и вы хотите изменить его более чем в 1 поток.
- вы можете создать копию объекта общей
- изменить общий объект
- вам нужно проверить, что общий объект по - прежнему такой же, как и раньше-если да, то обновите ссылку на измененную копию.
Как другой поток может изменить и можно изменить между этими 2 шагов. Вам нужно сделать это в атомной операции. вот где AtomicReference может помочь
Я не буду много говорить. Мои уважаемые друзья уже внесли свой ценный вклад. полноценный запущенный код в конце этого блога должен устранить любую путаницу. Речь идет о небольшой программе бронирования кинозалов в многопоточном сценарии.
некоторые важные элементарные факты заключаются в следующем. 1 > различные потоки могут конкурировать только, например, и статические переменные-члены в пространстве кучи. 2 > летучие чтения или записи являются полностью атомарными и сериализовано/происходит раньше и выполняется только из памяти. Говоря это, я имею в виду, что любое чтение будет следовать предыдущей записи в памяти. И любая запись будет следовать за предыдущим чтением из памяти. Таким образом, любой поток, работающий с volatile, всегда будет видеть самое актуальное значение. AtomicReference использует это свойство volatile.
Ниже приведены некоторые из исходного кода AtomicReference. Метод atomicreference относится к ссылке на объект. Эта ссылка является изменчивым элементом переменная в экземпляре 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(expect,update) вызывает метод compareAndSwapObject() небезопасного класса Java. Этот метод называют небезопасной вызывает нативную вызова, которая вызывается одним инструкция к процессору. "ожидать" и "обновить" каждую ссылку на объект.
если и только если переменная-член экземпляра AtomicReference " value "относится к тому же объекту, на который ссылается" expect"," update "теперь назначается этой переменной экземпляра и возвращается" true". Или же возвращается значение false. Все это делается автоматически. Никакой другой поток не может перехватить между ними. Поскольку это однопроцессорная операция (магия современной компьютерной архитектуры), она часто быстрее чем использование синхронизированного блока. Но помните, что когда несколько переменных должны быть обновлены атомарно, AtomicReference не поможет.
Я хотел бы добавить полноценный рабочий код, который можно запустить в Eclipse. Это прояснило бы многие путаницы. Здесь 22 пользователя (MyTh threads) пытаются забронировать 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поддержка lock-free потокобезопасного программирования на отдельных переменных.есть несколько способов достижения потокобезопасности с высоким уровнем одновременно API. Атомарные переменные-это один из нескольких вариантов.
Lockобъекты поддерживают блокировочные идиомы, которые упрощают многие параллельные приложения.
Executorsопределить высокоуровневый API для запуска и управления потоками. Реализации исполнителя, предоставляемые java.утиль.параллельное управление пулом потоков подходит для крупномасштабных приложений.параллельные коллекции упрощения управления большими коллекциями данных и может значительно уменьшить потребность в синхронизации.
атомные переменные есть функции, которые минимизируют синхронизацию и помогают избежать ошибок согласованности памяти.
приведите простой пример, где следует использовать 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/synchronizedetc.
вот очень простой случай использования и не имеет ничего общего с потокобезопасностью.
чтобы разделить объект между лямбда-вызовами,
AtomicReferenceвариант:public void doSomethingUsingLambdas() { AtomicReference<YourObject> yourObjectRef = new AtomicReference<>(); soSomethingThatTakesALambda(() -> { yourObjectRef.set(youObject); }); soSomethingElseThatTakesALambda(() -> { YourObject yourObject = yourObjectRef.get(); }); }Я не говорю, что это хороший дизайн или что-нибудь (это просто тривиальный пример), но если у вас есть случай, когда вам нужно разделить объект между лямбда-вызовами, то
AtomicReferenceвариант.на самом деле вы можете использовать любой объект, который содержит ссылку, даже Коллекция, которая имеет только один элемент. Тем не менее, AtomicReference идеально подходит.
еще один простой пример-это сделать безопасную модификацию потока в объекте сеанса.
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
Comments