Взаимное исключение против атомарной переменной

Атомарная операция — действие, которое фактически происходит сразу или не происходит вообще Пример: java.util.concurrent.atomic.AtomicInteger

Взаимное исключение — предотвращает одновременный доступ к общему ресурсу Пример: synchronized


С подходом взаимное исключение SynchronizedCounter является потокобезопасным,

class SynchronizedCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }

}

При использовании атомарной переменной AtomicCounter является потокобезопасным,

import java.util.concurrent.atomic.AtomicInteger;

class AtomicCounter {
    private AtomicInteger c = new AtomicInteger(0);

    public void increment() {
        c.incrementAndGet();
    }

    public void decrement() {
        c.decrementAndGet();
    }

    public int value() {
        return c.get();
    }
}

1) Почему в приведенном выше коде подход с атомарной переменной лучше, чем подход с взаимным исключением?

2) В общем, не совпадают ли цели подхода взаимное исключение и атомарная переменная?


person overexchange    schedule 02.12.2017    source источник
comment
Пожалуйста, укажите весь контекст в вопросе, не переходя по ссылкам.   -  person hyde    schedule 02.12.2017
comment
@hyde Запрос отредактирован   -  person overexchange    schedule 02.12.2017
comment
Это лучше, потому что он использует низкоуровневые инструкции, которые делают его быстрее, чем синхронизация, и потому что с его помощью сложнее ввести ошибку, чем с синхронизированным.   -  person JB Nizet    schedule 02.12.2017
comment
@JBNizet Обеспечивает ли подход atomic variable синхронизацию без блокировки?   -  person overexchange    schedule 02.12.2017
comment
Если ваша цель состоит в том, чтобы увеличивать целое число атомарно, да. Для более сложных вариантов использования (например, атомарное изменение двух ссылок) нет.   -  person JB Nizet    schedule 02.12.2017
comment
@JBNizet Зачем нужна такая терминология, как атомарная переменная (AtomicInteger), когда цель взаимного исключения та же?   -  person overexchange    schedule 02.12.2017
comment
Потому что цель вовсе не взаимные исключения. Цель состоит в том, чтобы атомарно увеличить целое число.   -  person JB Nizet    schedule 02.12.2017
comment
Подход @JBNizet Взаимное исключение может обеспечить атомарное увеличение целого числа, защищая тот критический раздел, который увеличивает его.   -  person overexchange    schedule 02.12.2017
comment
Да, и что? Вы спрашиваете, сможем ли мы выжить без атомарных переменных? да, мы могли бы. Они были представлены только в Java 6, кстати. Но они быстрее и безопаснее, как я объяснил в первом комментарии.   -  person JB Nizet    schedule 02.12.2017


Ответы (2)


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

Взаимные исключения позволяют координировать изменения нескольких переменных. Чтобы расширить ваш пример, представьте себе систему, которая обновляет два (или более) счетчика. Счетчики инициализируются следующим образом;

  • a = 0
  • b = 1

В приведенной ниже таблице каждая строка представляет транзакцию, которая приведет к желаемому состоянию. Каждый столбец представляет собой период времени (например, цикл ЦП).

Корректность для этой системы определяется следующим образом;

  1. устаревшие чтения разрешены (например, предыдущая транзакция целиком).
  2. частичные чтения недействительны (например, смешанное представление двух или более транзакций).

введите здесь описание изображения

Толстые черные линии представляют момент синхронизации во времени, когда значения могут быть прочитаны. С Atomics можно было бы выполнять в том порядке, в котором они показаны, что нежелательно. Взаимное исключение компрометирует производительность ради корректности, либо блокируя, либо предоставляя устаревшее чтение.

Чтобы понять, почему «правильность» важна, представьте, что «а» — это чистая прибыль, а «б» — валовая прибыль. Обычно предпочтительнее сообщить что-то в прошлом или сказать «1 момент», чем предоставлять значения, которые не складываются.

person Nathan    schedule 02.12.2017
comment
c.incrementandget() - это одна инструкция ЦП? - person overexchange; 03.12.2017
comment
Весь метод вряд ли будет одной инструкцией, однако атомарное приращение может быть достигнуто с помощью сборки lock; xaddl %0, %1. См. следующее для атомарного сложения; en.wikipedia.org/wiki/Fetch-and-add#x86_implementation Если у вас установлен декомпилятор хотспота, вы можете распечатать сборку с помощью следующей команды; java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly $MainClass Дополнительные сведения о выводе сборки см.; wiki.openjdk.java.net/display/HotSpot/PrintAssembly - person Nathan; 03.12.2017

Разница в том, что первая реализация с синхронизацией блокирует, а вторая — нет. Всестороннее описание различий и последствий для обоих подходов представлено в первых трех главах книги «Искусство многопроцессорного программирования».

Вот несколько утверждений из главы 3.7.

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

Условия прогресса для блокирующих реализаций: свойства без взаимоблокировок и голодания. Эти свойства являются зависимыми условиями прогресса: прогресс происходит только в том случае, если базовая платформа (т. е. операционная система) предоставляет определенные гарантии. В принципе, свойства отсутствия взаимоблокировок и голодания полезны, когда операционная система гарантирует, что каждый поток в конечном итоге покинет каждый критический раздел. На практике эти свойства полезны, когда операционная система гарантирует, что каждый поток в конечном итоге своевременно покинет каждую критическую секцию. Классы, методы которых полагаются на синхронизацию на основе блокировки, в лучшем случае могут гарантировать зависимые свойства хода выполнения. Означает ли это наблюдение, что следует избегать алгоритмов на основе блокировок? Не обязательно. Если вытеснение в середине критической секции происходит достаточно редко, то зависимые блокирующие условия прогресса фактически неотличимы от их неблокирующих аналогов. Если вытеснение достаточно распространено, чтобы вызывать опасения, или если стоимость задержки на основе вытеснения достаточно высока, то разумно рассмотреть неблокирующие условия выполнения.

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

Хорошим примером неблокирующей и блокирующей реализации одной и той же логики в Java являются ConcurrentLinkedQueue и LinkedBlockingQueue. В то время как LinkedBlockingQueue выглядит более привлекательным из-за неблокирующего свойства, иногда полезнее получить блокировку при постановке/удалении из очереди в ожидании новых элементов и предоставить время планирования другим потокам вместо немедленного получения пустого результата (null или исключения) и вращения в занятом цикле текущий поток.

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

person Nikita Gorbachevski    schedule 02.12.2017