AtomicReferenceFieldUpdater - методы устанавливают, получают, сравнивают семантику AndSet - PullRequest
28 голосов
/ 25 ноября 2011

с Java AtomicReferenceFieldUpdater документы :

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

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

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

Или я должен использовать get на AtomicReferenceFieldUpdater вместо энергозависимых чтений на поле?

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

Спасибо.

EDIT:

С Параллелизм Java на практике , единственное, что они говорят:

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

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

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

Ответы [ 3 ]

25 голосов
/ 30 ноября 2011

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

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

  • 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 поля с одинаковой атомарностью гарантируют частичное / непоследовательное чтение, переупорядочение операторов и т. Д.эта тема, вероятно, заинтересует обсуждение ниже.Меня попросили обновить ответ, чтобы уточнить основные моменты этого обсуждения:

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

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

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

    Итак:

    • В типичной реализации JDK для современного процессора AtomicReference.set просто использует энергозависимую запись, поскольку AtomicReference.compareAndSet использует операцию сравнения и замены, которая является атомарной по отношению к энергозависимой записи. AtomicReferenceUpdater.set обязательно более сложный, чем AtomicReference.set, потому что он должен использовать логику, подобную отражению, для обновления поля в другом объекте, но я утверждаю, что это причина only , потому что он более сложный. Типичная реализация вызывает 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); }
    }
    

    (примечание: пробел с поправкой на компактность), когда после создания экземпляра нет никаких изменчивых записей & mdash; то есть все записи выполняются через AtomicReferenceFieldUpdater.compareAndSet или AtomicReferenceFieldUpdater.set & mdash; но энергозависимые чтения, кажется, используются свободно, без единого вызова AtomicReferenceFieldUpdater.get. Более поздние выпуски JDK 1.6 были изменены для непосредственного использования Unsafe (это произошло в Oracle JDK 1.6.0_27), но обсуждения в списке рассылки JSR 166 связывают это изменение с соображениями производительности, а не с какими-либо сомнениями в правильности предыдущего осуществление.

    • Но я должен указать, что это не пуленепробиваемый авторитет. Для удобства я пишу о «реализации Sun», как если бы она была унитарной, но мой предыдущий пункт делает очевидным, что реализации JDK для разных платформ могут действовать по-разному. Мне кажется, что приведенный выше код был написан нейтральным от платформы способом, поскольку он избегает простых изменчивых записей в пользу вызовов AtomicReferenceFieldUpdater.set; но тот, кто не принимает мою интерпретацию одного пункта, может не принять мою интерпретацию другого и может утверждать, что приведенный выше код , а не предназначен для безопасности всех платформ.
    • Еще одним недостатком этого авторитета является то, что, хотя Node, по-видимому, допускает волатильное чтение одновременно с вызовами AtomicReferenceFieldUpdater.compareAndSet, это частный класс; и я не получил никаких доказательств того, что его владелец (ConcurrentLinkedQueue) действительно делает такие звонки без собственных мер предосторожности. (Но хотя я не доказал претензию, я сомневаюсь, что кто-либо будет оспаривать ее.)

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

2 голосов
/ 30 ноября 2011

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

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

sun.misc. У CAS 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.

2 голосов
/ 25 ноября 2011

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

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

...