Как атомарные / энергозависимые / синхронизированные работают внутри?
В чем разница между следующими блоками кода?
Код 1
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
Код 2
private AtomicInteger counter;
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
Код 3
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
Работает ли volatile
следующим образом? Является
volatile int i = 0;
void incIBy5() {
i += 5;
}
эквивалентно
Integer i = 5;
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Я думаю, что два потока не могут войти в синхронизированный блок одновременно ... я прав? Если это правда, то как atomic.incrementAndGet()
работает без synchronized
? И это потокобезопасно?
И в чем разница между внутренним чтением и записью в переменные / атомарные переменные? В какой-то статье я читал, что в потоке есть локальная копия переменных - что это?
Ответы:
Вы конкретно спрашиваете, как они работают внутри , так что вот вы:
Нет синхронизации
Он в основном считывает значение из памяти, увеличивает его и возвращает в память. Это работает в однопоточном режиме, но в настоящее время, в эпоху многоядерных, многоядерных, многоуровневых кэшей, оно не будет работать правильно. Прежде всего, он вводит состояние гонки (несколько потоков могут одновременно прочитать значение), но также проблемы с видимостью. Значение может храниться только в « локальной » памяти ЦП (некоторый кэш) и не быть видимым для других ЦП / ядер (и, следовательно, для потоков). Вот почему многие ссылаются на локальную копию переменной в потоке. Это очень небезопасно. Рассмотрим этот популярный, но неработающий код остановки потока:
Добавьте
volatile
кstopped
переменной, и она будет работать нормально - если какой-либо другой поток изменяетstopped
переменную черезpleaseStop()
метод, вы гарантированно увидите это изменение немедленно вwhile(!stopped)
цикле рабочего потока . Кстати, это тоже не хороший способ прервать поток, см .: Как остановить поток, который работает вечно без какого-либо использования, и Остановить определенный поток Java .AtomicInteger
AtomicInteger
Использует класс CAS ( сравнения и замены ) низкоуровневых операций ЦП (не требуется никакой синхронизации!) Они позволяют изменять определенную переменную , только если текущее значение равно что - то другое (и возвращается успешно). Поэтому, когда вы выполняетеgetAndIncrement()
его, он фактически запускается в цикле (упрощенная реальная реализация):Итак, в основном: читать; попытаться сохранить увеличенное значение; если не удалось (значение больше не равно
current
), прочитайте и попробуйте снова.compareAndSet()
Реализуются в машинном коде (сборка).volatile
без синхронизацииЭтот код не является правильным. Это исправляет проблему видимости (
volatile
гарантирует, что другие потоки могут видеть изменения, внесенные вcounter
), но все еще имеет состояние гонки. Это было объяснено несколько раз: до / после увеличения не является атомарным.Единственным побочным эффектом
volatile
является « очистка » кэшей, так что все остальные стороны видят самую свежую версию данных. Это слишком строго в большинстве ситуаций; вот почемуvolatile
не по умолчанию.volatile
без синхронизации (2)Та же проблема, что и выше, но еще хуже, потому что
i
нетprivate
. Состояние гонки все еще присутствует. Почему это проблема? Если, скажем, два потока запускают этот код одновременно, вывод может быть+ 5
или+ 10
. Тем не менее, вы гарантированно увидите изменения.Несколько независимых
synchronized
Сюрприз, этот код также неверен. На самом деле это совершенно неправильно. Во-первых, вы синхронизируете
i
, что должно быть изменено (более того,i
это примитив, поэтому я предполагаю, что вы синхронизируете временныйInteger
файл, созданный через автобокс ...). Вы также можете написать:Никакие два потока не могут войти в один и тот же
synchronized
блок с одинаковой блокировкой . В этом случае (и аналогично в вашем коде) объект блокировки изменяется при каждом выполнении, поэтомуsynchronized
эффективно не действует.Даже если вы использовали конечную переменную (или
this
) для синхронизации, код все равно неверен. Две нити могут сначала прочитать ,i
чтобыtemp
синхронно (имеющие одинаковое значение локально вtemp
), то первое присваивает новое значениеi
(скажем, от 1 до 6) , а другой делает то же самое (от 1 до 6).Синхронизация должна охватывать от чтения до присвоения значения. Ваша первая синхронизация не имеет никакого эффекта (чтение
int
является атомарным), а также вторая. На мой взгляд, это правильные формы:источник
compareAndSet
это просто тонкая оболочка для операции CAS. Я вхожу в некоторые детали в моем ответе.Объявление переменной как volatile означает, что изменение ее значения немедленно влияет на фактическое хранение в памяти переменной. Компилятор не может оптимизировать любые ссылки, сделанные на переменную. Это гарантирует, что когда один поток изменяет переменную, все остальные потоки видят новое значение немедленно. (Это не гарантируется для энергонезависимых переменных.)
Объявление атомарной переменной гарантирует, что операции, выполняемые с этой переменной, происходят атомарным образом, то есть все подэтапы операции выполняются в потоке, в котором они выполняются, и не прерываются другими потоками. Например, операция увеличения и проверки требует, чтобы переменная была увеличена, а затем сравнена с другим значением; атомарная операция гарантирует, что оба эти шага будут выполнены, как если бы они были единой неделимой / непрерывной операцией.
Синхронизация всех обращений к переменной позволяет только одному потоку одновременно обращаться к переменной и заставляет все остальные потоки ждать, когда этот доступный поток освободит свой доступ к переменной.
Синхронизированный доступ аналогичен атомарному доступу, но атомарные операции обычно реализуются на более низком уровне программирования. Кроме того, вполне возможно синхронизировать только некоторые обращения к переменной и разрешить несинхронизировать другие обращения (например, синхронизировать все записи в переменную, но ни одно из чтений из нее).
Атомарность, синхронизация и изменчивость являются независимыми атрибутами, но обычно используются в комбинации для обеспечения правильного взаимодействия потоков для доступа к переменным.
Приложение (апрель 2016 г.)
Синхронизированный доступ к переменной обычно реализуется с помощью монитора или семафора . Это низкоуровневые мьютекс (взаимное исключение) механизмы, позволяющие нить для контроля приобретшего переменного или блока коды исключительно, заставляя все остальные потоки ждать , если они также пытаются получить один и тот же семафор. Как только поток-владелец освобождает мьютекс, другой поток может получить его по очереди.
Приложение (июль 2016 г.)
Синхронизация происходит на объекте . Это означает, что вызов синхронизированного метода класса заблокирует
this
объект вызова. Статические синхронизированные методы заблокируют самClass
объект.Аналогично, для ввода синхронизированного блока требуется блокировка
this
объекта метода.Это означает, что синхронизированный метод (или блок) может выполняться в нескольких потоках одновременно, если они блокируются на разных объектах, но только один поток может одновременно выполнять синхронизированный метод (или блок) для любого данного отдельного объекта.
источник
летучий:
volatile
это ключевое слово.volatile
вынуждает все потоки получать последнее значение переменной из основной памяти вместо кеша. Для доступа к переменным переменным блокировка не требуется. Все потоки могут получить доступ к значению переменной переменной одновременно.Использование
volatile
переменных снижает риск ошибок согласованности памяти, потому что любая запись в энергозависимую переменную устанавливает связь «произойдет до» с последующим чтением этой же переменной.Это означает, что изменения в
volatile
переменной всегда видны другим потокам . Более того, это также означает, что когда поток читаетvolatile
переменную, он видит не только последние изменения в volatile, но также побочные эффекты кода, который привел к изменению .Когда использовать: один поток изменяет данные, а другие потоки должны прочитать последнее значение данных. Другие потоки предпримут некоторые действия, но они не будут обновлять данные .
AtomicXXX:
AtomicXXX
классы поддерживают безблокировочное поточно-ориентированное программирование для отдельных переменных. ЭтиAtomicXXX
классы (какAtomicInteger
) устраняют ошибки несоответствия памяти / побочные эффекты модификации изменчивых переменных, которые были доступны в нескольких потоках.Когда использовать: Несколько потоков могут читать и изменять данные.
синхронизируются:
synchronized
ключевое слово, используемое для защиты метода или блока кода. Создание синхронизированного метода имеет два эффекта:Во-первых, два вызова одного и
synchronized
того же объекта не могут чередоваться. Когда один поток выполняетsynchronized
метод для объекта, все другие потоки, которые вызываютsynchronized
методы для того же блока объекта (приостанавливают выполнение), пока первый поток не завершится с объектом.Во-вторых, когда
synchronized
метод завершается, он автоматически устанавливает отношение «до и после» с любым последующим вызовомsynchronized
метода для того же объекта. Это гарантирует, что изменения состояния объекта видны всем потокам.Когда использовать: Несколько потоков могут читать и изменять данные. Ваша бизнес-логика не только обновляет данные, но и выполняет атомарные операции
AtomicXXX
эквивалентно,volatile + synchronized
хотя реализация отличается.AmtomicXXX
расширяетvolatile
переменные +compareAndSet
методы, но не использует синхронизацию.Связанные вопросы SE:
Разница между изменчивым и синхронизированным в Java
Летучий логический против AtomicBoolean
Хорошие статьи для чтения: (выше содержание взято из этих страниц документации)
https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html
https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html
источник
Два потока не могут войти в синхронизированный блок на одном и том же объекте дважды. Это означает, что два потока могут входить в один и тот же блок на разных объектах. Эта путаница может привести к такому коду.
Это не будет вести себя так, как ожидалось, поскольку каждый раз может блокироваться на другом объекте.
да. Он не использует блокировку для достижения безопасности потока.
Если вы хотите узнать, как они работают более подробно, вы можете прочитать код для них.
Атомный класс использует изменчивые поля. Там нет никакой разницы в области. Разница в выполняемых операциях. Классы Atomic используют операции CompareAndSwap или CAS.
Я могу только предположить, что это относится к тому факту, что каждый процессор имеет свое собственное кэшированное представление памяти, которое может отличаться от любого другого процессора. Для того, чтобы ваш ЦП имел согласованное представление данных, вам необходимо использовать методы обеспечения безопасности потоков.
Это проблема, только когда память распределяется, хотя бы один поток обновляет ее.
источник
Синхронизированный против атомного против летучего:
Пожалуйста, поправьте меня, если я что-то пропустил.
источник
Синхронизация volatile + - это надежное решение для операции (оператора), которая должна быть полностью атомарной, которая включает в себя несколько инструкций для CPU.
Скажем, например: volatile int i = 2; i ++, который является ничем иным, как i = i + 1; что делает i как значение 3 в памяти после выполнения этого оператора. Это включает в себя считывание существующего значения из памяти для i (которое равно 2), загрузку в регистр аккумулятора ЦП и выполнение вычисления путем увеличения существующего значения на единицу (2 + 1 = 3 в аккумуляторе), а затем запись этого увеличенного значения обратно. назад в память. Эти операции не являются достаточно атомарными, хотя значение i является изменчивым. Будучи энергозависимым, я гарантирую только то, что ЕДИНОЕ чтение / запись из памяти является атомарным, а не МНОГОКРАТНЫМ. Следовательно, мы должны синхронизироваться и вокруг i ++, чтобы это было надежным атомарным утверждением. Помните тот факт, что утверждение включает в себя несколько утверждений.
Надеюсь, объяснение достаточно ясно.
источник
Модификатор Java volatile является примером специального механизма, гарантирующего взаимодействие между потоками. Когда один поток выполняет запись в энергозависимую переменную, а другой поток видит эту запись, первый поток сообщает второму обо всем содержимом памяти до тех пор, пока не выполнит запись в эту переменную переменную.
Атомарные операции выполняются в одной единице задачи без вмешательства других операций. Атомарные операции необходимы в многопоточной среде, чтобы избежать несогласованности данных.
источник