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 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) на самом деле делает такие звонки без собственных мер предосторожности. (Но хотя я не доказал претензию, я сомневаюсь, что кто-либо будет оспаривать ее.)

Пожалуйста, смотрите комментарии ниже для справки по этому приложению и для дальнейшего обсуждения.

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

Единственный способ, который может быть гарантирован, - это если поля были окончательными или изменчивыми.

Это не будет точным ответом на вопрос:

Ни объяснение, ни намерение не ясно видно из документации. Если бы идея состояла в том, чтобы обойти глобальное упорядочение, то есть изменчивую запись в архитектурах, которые позволяют это [например, 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.

Другие вопросы по тегам