AtomicInteger и неустойчивый

Я знаю, что volatile допускает видимость, AtomicInteger допускает атомарность. Поэтому, если я использую volatile AtomicInteger, значит ли это, что мне больше не нужно использовать механизмы синхронизации?

Eg.

class A {

    private volatile AtomicInteger count;

    void someMethod(){
        // do something
        if(count.get() < 10) {
            count.incrementAndGet();
        }
}

Является ли это потокобезопасным?

Ответ 1

Я считаю, что Atomic* фактически дает как атомарность, так и волатильность. Поэтому, когда вы звоните (скажем) AtomicInteger.get(), вам гарантировано получить последнее значение. Это описано в документации пакета java.util.concurrent.atomic :

Эффекты памяти для доступа и обновлений атоматики обычно следуют правилам для летучих, как указано в разделе 17.4 Спецификации языка Java ™.

  • get имеет эффект памяти при чтении изменчивой переменной.
  • set имеет эффект памяти записи (назначения) изменчивой переменной.
  • lazySet имеет эффекты памяти при записи (присвоении) изменчивой переменной, за исключением того, что она позволяет переупорядочивать с последующими (но не предыдущими) операциями памяти, которые сами не налагают ограничения переупорядочения обычными нелетучими записями. Среди других контекстов использования > - lazySet может применяться при обнулении, ради сбора мусора, ссылки, которая никогда не обращается снова.
  • weakCompareAndSet атомарно считывает и условно записывает переменную, но не создает каких-либо событий перед заказами, поэтому не дает никаких гарантий относительно предыдущих или последующих чтений и записей любых переменных, отличных от целевого объекта weakCompareAndSet.
  • compareAndSet и все другие операции чтения и обновления, такие как getAndIncrement, имеют эффекты памяти как для чтения, так и для записи изменчивых переменных.

Теперь, если у вас есть

volatile AtomicInteger count;

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

Это не часто (IME), чтобы это нужно - потому что обычно вы не переназначали count для обращения к другому объекту. Вместо этого у вас будет:

private final AtomicInteger count = new AtomicInteger();

В тот момент факт, что переменная a final означает, что все потоки будут иметь дело с одним и тем же объектом, и тот факт, что объект Atomic* означает, что они будут видеть самое последнее значение внутри этого объекта.

Ответ 2

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

Проблема в том, что get и incrementAndGet являются атомарными, а if - нет. Имейте в виду, что неатомная операция может быть приостановлена ​​в любое время. Например:

  • count = 9 в настоящее время.
  • Thread A запускает if(count.get() <10) и получает true и останавливается там.
  • Thread B запускает if(count.get() <10) и получает true тоже, поэтому он запускает count.incrementAndGet() и заканчивает. Теперь count = 10.
  • Thread A возобновляет и запускает count.incrementAndGet(), теперь count = 11, который никогда не будет выполняться в однопоточном режиме.

Если вы хотите сделать его потокобезопасным, не используя synchronized, который работает медленнее, попробуйте выполнить эту реализацию:

class A{

final AtomicInteger count;

void someMethod(){
// do something
  if(count.getAndIncrement() <10){
      // safe now
  } else count.getAndDecrement(); // rollback so this thread did nothing to count
}

Ответ 4

Чтобы сохранить исходную семантику и поддерживать несколько потоков, вы можете сделать что-то вроде:

public class A {

    private AtomicInteger count = new AtomicInteger(0);

    public void someMethod() {

        int i = count.get();
        while (i < 10 && !count.compareAndSet(i, i + 1)) {
            i = count.get();
        }

    }

}

Это позволяет избежать любого потока, когда-либо видящего счетчик досягаемости 10.

Ответ 5

На ваш запрос можно ответить в 2 частях, потому что в вашем запросе есть 2 вопроса:

1) Ссылаясь на учебную документацию Oracle для атомарных переменных: https://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html

Пакет java.util.concurrent.atomic определяет классы, которые поддерживают атомарные операции над отдельными переменными. Все классы имеют методы get и set, которые работают как чтение и запись по переменным переменным. То есть набор имеет отношение "происходит до" с любым последующим получением той же переменной. У атомарного метода compareAndSet также есть эти особенности согласованности памяти, также как и у простых атомарных арифметических методов, которые применяются к целочисленным атомным переменным.

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

Вы должны посмотреть бесплатное видео Джона Пурселла на Udemy, где он показывает сбой ключевого слова volatile, когда несколько потоков пытаются его изменить. Простой и красивый пример. https://www.udemy.com/course/java-multithreading/learn/lecture/108950#overview

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

2) Подходим к вашему коду: Скажем, поток 1 начинает работу, а someMethod получает и проверяет размер. Возможно, что перед выполнением getAndIncrement (скажем, потоком 1), другой поток (скажем, поток 2) вступает в действие, увеличивает счет до 10 и выходит; после чего ваш поток 1 возобновит работу и увеличит счет до 11. Это ошибочный вывод. Это потому, что ваш "someMethod" в любом случае не защищен от проблем с синхронизацией. Я по-прежнему рекомендую вам посмотреть видео Джона Пёрселла, чтобы увидеть, где происходит сбой volatile, чтобы вы лучше понимали ключевое слово volatile. Замените его на atomicinteger в его примере и увидите магию.