AtomicReferenceFieldUpdater - методы устанавливают, получают, сравнивают семантику AndSet
С Явы AtomicReferenceFieldUpdater
документы:
Обратите внимание, что гарантии
compareAndSet
Метод в этом классе слабее, чем в других атомных классах. Поскольку этот класс не может гарантировать, что все виды использования поля подходят для целей атомарного доступа, он может гарантировать атомарность и изменчивую семантику только в отношении других вызововcompareAndSet
а такжеset
,
Это означает, что я не могу делать нормальные летучие записи вместе с compareAndSet
, но должен использовать set
вместо. Ничего не говорится о get
,
Означает ли это, что я все еще могу читать изменчивые поля с теми же гарантиями атомарности - все записи до set
или же compareAndSet
видны всем, кто читал волатильное поле?
Или я должен использовать get
на AtomicReferenceFieldUpdater
вместо летучих читает на поле?
Пожалуйста, разместите ссылки, если они у вас есть.
Спасибо.
РЕДАКТИРОВАТЬ:
Из Java Concurrency на практике единственное, что они говорят:
Гарантии атомарности для классов обновлений слабее, чем для обычных атомарных классов, потому что вы не можете гарантировать, что базовые поля не будут изменены напрямую - методы CompareAndSet и арифметика гарантируют атомарность только по отношению к другим потокам, использующим методы обновления атомных полей.
Опять же, нет упоминания о том, как другие потоки должны читать эти изменчивые поля.
Кроме того, могу ли я предположить, что "изменение напрямую" - это обычная изменчивая запись?
3 ответа
Как объяснено в документации по пакету для атомных (в общем, не для обновлений):
Эффекты памяти для доступа и обновлений атомных элементов обычно следуют правилам для летучих, [...]:
get
имеет эффект памяти чтенияvolatile
переменная.set
имеет эффект памяти записи (назначения)volatile
переменная.- [...]
compareAndSet
и все другие операции чтения и обновления, такие какgetAndIncrement
имеют эффекты памяти как чтения, так и письмаvolatile
переменные.
В чем проблема атома? compareAndSet
пытаясь решить? Зачем использовать (например) atomicInteger.compareAndSet(1,2)
вместоif(volatileInt == 1) { volatileInt = 2; }
? Он не пытается решить любую проблему с одновременным чтением, потому что об этом уже заботится регулярный volatile
, ("Волатильное" чтение или запись - это то же самое, что и "атомарное" чтение или запись. Одновременное чтение будет проблемой только в том случае, если это произошло в середине записи, или если операторы были переупорядочены или оптимизированы каким-либо проблемным способом; но volatile
это уже мешает.) Единственная проблема, которая compareAndSet
решает, что вvolatileInt
подход, некоторые другие потоки могут прийти с одновременной записью, между тем, когда мы читаемvolatileInt
(volatileInt == 1
) и когда мы напишем к нему (volatileInt = 2
).compareAndSet
решает эту проблему, блокируя любые конкурирующиезаписи в течение этого времени.
Это в равной степени относится и к конкретному случаю "обновлений" (AtomicReferenceFieldUpdater
так далее.):volatile
читает все еще просто персиковый. АпдейтерыcompareAndSet
Единственное ограничение методов состоит в том, что вместо "блокировки любых конкурирующих записей", как я писал выше, они блокируют только конкурирующие записииз одного и того же экземпляра AtomicReferenceFieldUpdater
; они не могут защитить вас, когда вы одновременно обновляете volatile
поле (или, в этом отношении, когда вы одновременно используете несколько AtomicReferenceFieldUpdater
s, чтобы обновить то же самое volatile
поле). (Кстати, в зависимости от того, как вы на это смотрите - то же самое относится и к AtomicReference
и его родство: если бы вы обновили их поля таким образом, чтобы обойти их собственных сеттеров, они не смогли бы вас защитить. Разница в том, чтоAtomicReference
на самом деле владеет своим полем, и этоprivate
, поэтому нет необходимости предупреждать вас о том, чтобы каким-либо образом изменять его внешними средствами.)
Итак, чтобы ответить на ваш вопрос: да, вы можете продолжить читатьvolatile
поля с одинаковой атомарностью гарантируют частичное / непоследовательное чтение, перестановку операторов и т. д.
Отредактировано, чтобы добавить(6 декабря): Любой, кто особенно интересуется этой темой, вероятно, будет заинтересован в обсуждении непосредственно ниже. Меня попросили обновить ответ, чтобы уточнить основные моменты этого обсуждения:
Я думаю, что самым важным моментом, который следует добавить, является то, что вышеизложенное является моей собственной интерпретацией документации. Я довольно уверен, что правильно понял и что никакая другая интерпретация не имеет смысла; и я могу, при желании, аргументировать точку зрения подробно;-); но ни я, ни кто-либо другой не привел никаких ссылок на какой-либо авторитетный документ, который затрагивает этот вопрос более явно, чем два документа, упомянутых в самом вопросе (Javadoc класса ипараллелизм Java на практике) и один документ, упомянутый в моем первоначальном ответе на это выше (Javadoc пакета).
Следующим наиболее важным моментом, я думаю, является то, что, хотя документация для
AtomicReferenceUpdater
говорит, что смешивать небезопасноcompareAndSet
с нестабильной записью, я считаю, что на типичных платформах это на самом деле безопасно. Это небезопасно только в общем случае. Я говорю это из-за следующего комментария из документации пакета:Спецификации этих методов позволяют реализациям использовать эффективные атомарные инструкции машинного уровня, доступные на современных процессорах. Однако на некоторых платформах поддержка может повлечь за собой некоторую форму внутренней блокировки. Таким образом, методы строго не гарантированы как неблокирующие - поток может временно блокироваться перед выполнением операции.
Так:
- В типичной реализации JDK для современного процессора,
AtomicReference.set
просто использует изменчивую запись, так какAtomicReference.compareAndSet
использует операцию сравнения и замены, которая является атомарной по отношению к изменчивым операциям записи.AtomicReferenceUpdater.set
обязательно сложнее, чемAtomicReference.set
потому что он должен использовать отражающую логику для обновления поля в другом объекте, но я утверждаю, что это единственная причина, по которой он более сложен. Типичная реализация вызововUnsafe.putObjectVolatile
, который является изменчивой записью под более длинным именем. - Но не все платформы поддерживают этот подход, и если они этого не делают, то блокировка разрешена. На риск упрощения, я понимаю, что это примерно означает, что атомный класс
compareAndSet
может быть реализовано (более или менее) путем примененияsynchronized
к методу, который используетget
а такжеset
прямолинейно. Но чтобы это работало,set
также должен бытьsynchronized
по причине, изложенной в моем первоначальном ответе выше; то есть, это не может быть просто изменчивая запись, потому что тогда она может изменить поле послеcompareAndSet
позвонилget
но преждеcompareAndSet
звонкиset
, - Излишне говорить, что в моем первоначальном ответе использование фразы "блокировка" не должно восприниматься буквально, поскольку на типичной платформе ничего очень похожего на блокировку не возникает.
- В типичной реализации JDK для современного процессора,
В реализации JDK 1.6.0_05 от Sun
java.util.concurrent.ConcurrentLinkedQueue<E>
мы находим это:private static class Node<E> { private volatile E item; private volatile Node<E> next; private static final AtomicReferenceFieldUpdater<Node, Node> nextUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next"); private static final AtomicReferenceFieldUpdater<Node, Object> itemUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Object.class, "item"); Node(E x) { item = x; } Node(E x, Node<E> n) { item = x; next = n; } E getItem() { return item; } boolean casItem(E cmp, E val) { return itemUpdater.compareAndSet(this, cmp, val); } void setItem(E val) { itemUpdater.set(this, val); } Node<E> getNext() { return next; } boolean casNext(Node<E> cmp, Node<E> val) { return nextUpdater.compareAndSet(this, cmp, val); } void setNext(Node<E> val) { nextUpdater.set(this, val); } }
(примечание: пробел с поправкой на компактность), когда после создания экземпляра нет никаких изменчивых записей, то есть все записи выполняются через
AtomicReferenceFieldUpdater.compareAndSet
или жеAtomicReferenceFieldUpdater.set
- но изменчивые чтения, кажется, используются свободно, без единого вызоваAtomicReferenceFieldUpdater.get
, Более поздние выпуски JDK 1.6 были изменены для использованияUnsafe
напрямую (это произошло в Oracle JDK 1.6.0_27), но обсуждения в списке рассылки JSR 166 связывают это изменение с соображениями производительности, а не с какими-либо сомнениями в правильности предыдущей реализации.- Но я должен отметить, что это не пуленепробиваемый авторитет. Для удобства я пишу о "реализации Sun", как если бы она была унитарной, но мой предыдущий пункт делает очевидным, что реализации JDK для разных платформ могут делать вещи по-разному. Мне кажется, что приведенный выше код был написан нейтральным от платформы способом, поскольку он избегает простых изменчивых записей в пользу обращений к
AtomicReferenceFieldUpdater.set
; но тот, кто не принимает мою интерпретацию одного пункта, может не принять мою интерпретацию другого и может утверждать, что приведенный выше код не предназначен для безопасности для всех платформ. - Еще одна слабость этой власти заключается в том, что, хотя
Node
кажется, что волатильное чтение происходит одновременно с вызовамиAtomicReferenceFieldUpdater.compareAndSet
это частный класс; и я не предпринял никаких доказательств того, что его владелец (ConcurrentLinkedQueue
) на самом деле делает такие звонки без собственных мер предосторожности. (Но хотя я не доказал претензию, я сомневаюсь, что кто-либо будет оспаривать ее.)
- Но я должен отметить, что это не пуленепробиваемый авторитет. Для удобства я пишу о "реализации Sun", как если бы она была унитарной, но мой предыдущий пункт делает очевидным, что реализации JDK для разных платформ могут делать вещи по-разному. Мне кажется, что приведенный выше код был написан нейтральным от платформы способом, поскольку он избегает простых изменчивых записей в пользу обращений к
Пожалуйста, смотрите комментарии ниже для справки по этому приложению и для дальнейшего обсуждения.
Это означает, что ссылка на объект будет гарантирована, но поскольку вы можете использовать любой объект, поля этого объекта могут быть неправильно записаны, когда другой поток перейдет к объекту.
Единственный способ, который может быть гарантирован, - это если поля были окончательными или изменчивыми.
Это не будет точным ответом на вопрос:
Ни объяснение, ни намерение не ясно видно из документации. Если бы идея состояла в том, чтобы обойти глобальное упорядочение, то есть изменчивую запись в архитектурах, которые позволяют это [например, IBM Power или ARM], и просто выставить поведение CAS (LoadLinked/StoreCondition) БЕЗ ограждения, это было бы довольно удивительным усилием и источником путаницы.
CAS sun.misc.Unsafe не имеет никаких спецификаций или гарантий упорядочения (известных как раньше), но java.util.atomic... делает. Так что на более слабой модели java.util.atomic impl. в этом случае потребуются необходимые ограждения для соответствия спецификации Java.
Предполагая, что классы Updater на самом деле не имеют заборов. Если они делают это, volatile чтение поля (без использования get) должно вернуть значение обновления, т.е. get()
не требуется Поскольку не будет гарантий заказа, предыдущие магазины могут не распространяться (на слабых моделях). На x86/Sparc TSO аппаратное обеспечение обеспечивает спецификацию Java.
Однако это также означает, что CAS можно переупорядочить с помощью следующих энергонезависимых операций чтения. Есть интересное примечание от java.util.concurrent.SynchronousQueue
очередь:
// Note: item and mode fields don't need to be volatile
// since they are always written before, and read after,
// other volatile/atomic operations.
Все упомянутые атомарные операции являются точно CAS AtomicReferenceFieldUpdater. Это подразумевало бы отсутствие или переписывание между обычными операциями чтения и записи и AtomicReferenceFieldUpdater.CAS, то есть действующими как энергозависимая запись.
s.item = null; // forget item
s.waiter = null; // forget thread
//....
while ((p = head) != null && p != past && p.isCancelled())
casHead(p, p.next);
Просто CAS, нет летучих пишет.
Учитывая приведенное выше условие, я бы пришел к выводу, что AtomicXXXFieldUpdater предоставляет ту же семантику, что и их аналоги AtomicXXX.