Подтвердить что ты не робот

AtomicReferenceFieldUpdater - набор методов, get, compareAndSet

В Java AtomicReferenceFieldUpdater docs:

Обратите внимание, что гарантии метода compareAndSet в этом классе слабее, чем в других атомных классах. Потому что этот класс не может обеспечить что все применения поля подходят для атомных доступа, он может гарантировать атомарность и изменчивую семантику только с по отношению к другим вызовам compareAndSet и set.

Это означает, что я не могу выполнять нормальные волатильные записи вместе с compareAndSet, но вместо этого должен использовать set. Он ничего не говорит о get.

Означает ли это, что я все еще могу читать изменчивые поля с одинаковыми гарантиями на атомарность - все записи перед set или compareAndSet видны всем, кто прочитал поле volatile?

Или мне нужно использовать get в AtomicReferenceFieldUpdater вместо летучих чтений в поле?

Пожалуйста, разместите ссылки, если они у вас есть.

Спасибо.

EDIT:

От Java Concurrency на практике, единственное, что они говорят:

Гарантии атомарности для классов обновления более слабы, чем для обычные атомные классы, потому что вы не можете гарантировать, что базовые поля не будут изменены напрямую - compareAndSet и арифметические методы гарантируют атомарность только по отношению к другим потоков, использующих методы обновления атомного поля.

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

Кроме того, могу ли я предположить, что "измененный напрямую" является регулярной изменчивой записью?

4b9b3361

Ответ 1

Как объяснено в документации пакета для атомистики (в общем, не для уточнений):

Эффекты памяти для доступа и обновлений атоматики обычно следуют правилам для летучих, [...]:

  • 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 решает эту проблему, блокируя любые конкурирующие записи за это время.

Это одинаково верно в конкретном случае "updaters" (AtomicReferenceFieldUpdater и т.д.): volatile чтения по-прежнему просто персиковые. Единственным ограничением методов compareAndSet updaters является то, что вместо "блокировки любых конкурирующих записей", как я писал выше, они блокируют только конкурирующие записи из того же экземпляра AtomicReferenceFieldUpdater; они не могут защитить вас, когда вы одновременно обновляете поле volatile напрямую (или, если на то пошло, когда вы одновременно используете несколько AtomicReferenceFieldUpdater для обновления того же поля volatile). (Кстати, в зависимости от того, как вы смотрите на это: то же самое относится к AtomicReference и его родственникам: если бы вы обновляли свои поля таким образом, чтобы обойти свои собственные сеттеры, они не могли защитить вас. a AtomicReference действительно владеет своим полем, а это private, поэтому нет необходимости предупреждать вас о том, чтобы каким-то образом его модифицировать внешними средствами.)

Итак, чтобы ответить на ваш вопрос: да, вы можете продолжать читать поля volatile с теми же гарантиями атомарности против частичных/несогласованных чтений, переупорядочиваемых операторов и т.д.


Отредактировано для добавления (6 декабря): Любой, кто особенно заинтересован в этом вопросе, вероятно, будет заинтересован в обсуждении сразу же. Меня попросили уточнить ответ, чтобы уточнить важные моменты из этого обсуждения:

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

  • Следующим наиболее важным моментом, я думаю, является то, что, хотя в документации для AtomicReferenceUpdater говорится, что небезопасно смешивать compareAndSet с volatile write, я считаю, что на типичных платформах это действительно безопасно. Это небезопасно только в общем случае. Я говорю об этом из-за следующего комментария из документации пакета:

    Спецификации этих методов позволяют реализациям использовать эффективные атомные инструкции на уровне машины, доступные на современных процессорах. Однако на некоторых платформах поддержка может повлечь за собой некоторую форму внутренней блокировки. Таким образом, методы строго не гарантируются как неблокирующие - поток может временно блокироваться перед выполнением операции.

    Итак:

    • В типичной реализации JDK для современного процессора AtomicReference.set просто использует volatile write, так как AtomicReference.compareAndSet использует операцию сравнения и замены, которая является атомарной относительно летучих записей. AtomicReferenceUpdater.set обязательно более сложный, чем AtomicReference.set, потому что он должен использовать рефлексивную логику для обновления поля в другом объекте, но я утверждаю, что это единственная причина, по которой это сложнее. Типичная реализация вызывает Unsafe.putObjectVolatile, которая является volatile write более длинным именем.
    • Но не все платформы поддерживают этот подход, и если они этого не делают, блокировка разрешена. Рискуя упростить, я полагаю, что это означает, что атомный класс compareAndSet может быть реализован (более или менее) с применением synchronized к методу, который использует get и set прямо. Но для этого, set также должен быть synchronized, по причине, описанной в моем первоначальном ответе выше; то есть он не может быть просто volatile write, потому что тогда он может изменить поле после того, как compareAndSet вызвал get, но до compareAndSet вызывает set.
    • Излишне говорить, что мой оригинальный ответ использования фразы "блокировка" не следует воспринимать буквально, так как на типичной платформе не возникает ничего, что связано с блокировкой.
  • В Sun JDK 1.6.0_05 реализации 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) фактически совершает такие звонки без своих собственных мер предосторожности. (Но, хотя я не доказал свою претензию, я сомневаюсь, что кто-нибудь будет оспаривать ее.)

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

Ответ 2

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

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

Ответ 3

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

Ни объяснение, ни намерение не ясны из документации. Если идея заключалась в том, чтобы обойти глобальное упорядочение, например, volatile write на архитектурах, которые позволяют ему (например, IBM Power или ARM) и просто выставлять поведение CAS (LoadLinked/StoreCondition) БЕЗ ограждений, это было бы удивительным усилием и источником путаницы.

sun.misc.Unsafe CAS не имеет спецификаций или заказов (известных как бывает раньше), но java.util.atomic... делает. Так на более слабой модели java.util.atomic impl. в этом случае потребуются необходимые заборы, чтобы следовать спецификации java.

Предполагая, что классы Updater фактически не имеют заборов. Если они это сделают, волатильное чтение поля (без использования 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. Это означало бы отсутствие или запись между обычными чтениями AND и AtomicReferenceFieldUpdater.CAS, т.е. Такими, как volatile write.

        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.