В чем разница между atomic / volatile / synchronized?
как атомная / Летучая / синхронизированная работа внутри?
в чем разница между следующими блоками кода?
код 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? И это потокобезопасно?
и в чем разница между внутренним чтением и записью в изменчивые переменные / атомарные переменные? Я читал в какой - то статье, что поток имеет локальную копию переменных-что это такое?
6 ответов:
вы конкретно спрашиваете о том, как они внутренне работать, так вот:
нет синхронизации
private int counter; public int getNextUniqueIndex() { return counter++; }он в основном считывает значение из памяти, увеличивает его и возвращает в память. Это работает в одном потоке, но в настоящее время, в эпоху многоядерных, многопроцессорных, многоуровневых кэшей он не будет работать правильно. Прежде всего, он вводит условие гонки (несколько потоков могут одновременно считывать значение), но и видимость проблемы. Значение может храниться только в "местные " память процессора (некоторый кэш) и не будет видна для других процессоров/ядер (и, следовательно, - потоков). Вот почему многие ссылаются на локальная копия переменной в потоке. Это очень небезопасно. Рассмотрим этот популярный, но сломанный код остановки потока:
private boolean stopped; public void run() { while(!stopped) { //do some work } } public void pleaseStop() { stopped = true; }добавить
volatileдоstoppedпеременная и она отлично работает, если любой другой поток изменяетstoppedпеременной черезpleaseStop()метод, вы гарантированно увидите, что изменить сразу в рабочем потокеwhile(!stopped)петли. Кстати, это тоже не лучший способ прервать поток, см.:как остановить поток, который работает вечно без какого-либо использования и остановка определенного потока java.
AtomicIntegerprivate AtomicInteger counter = new AtomicInteger(); public int getNextUniqueIndex() { return counter.getAndIncrement(); }The
AtomicIntegerкласс использует CAS (compare-and-swap) низкоуровневые операции ЦП (синхронизация не требуется!) Они позволяют изменять определенную переменную только в том случае, если присутствует значение равно чему-то еще (и возвращается успешно). Поэтому, когда вы выполняетеgetAndIncrement()он фактически работает в цикле (упрощенная реальная реализация):int current; do { current = get(); } while(!compareAndSet(current, current + 1));так что в основном: читать; попробуйте сохранить увеличенное значение; если не удалось (значение больше не равно
current), прочитайте и повторите попытку. ЭлементcompareAndSet()реализовано в машинном коде (сборка).
volatileбез синхронизацииprivate volatile int counter; public int getNextUniqueIndex() { return counter++; }этот код неверен. Он исправляет проблема видимости (
volatileубедитесь, что другие потоки могут видеть изменения, внесенные вcounter), но все еще имеет состояние гонки. Это было объяснил несколько раз: pre / post-incrementation не является атомарным.единственный побочный эффект
volatileэто "промывка" кэширует так, чтобы все остальные стороны видели самую свежую версию данных. Это слишком строго в большинстве ситуаций; вот почемуvolatileне по умолчанию.
volatileбез синхронизации (2)volatile int i = 0; void incIBy5() { i += 5; }та же проблема, что и выше, но еще хуже, потому что
iнеprivate. Состояние гонки все еще присутствует. Почему это проблема? Если, скажем, два потока выполняют этот код одновременно, выход может быть+ 5или+ 10. Тем не менее, вы гарантированно увидите изменения.несколько независимых
synchronizedvoid incIBy5() { int temp; synchronized(i) { temp = i } synchronized(i) { i = temp + 5 } }сюрприз, этот код также неверен. На самом деле, это совершенно неправильно. Прежде всего, вы синхронизация на
i, который вот-вот будет изменен (более того,iэто примитив, поэтому я думаю, что вы синхронизируетесь на временнойIntegerсоздано с помощью автобоксинга...) Полностью испорчен. Вы также можете написать:synchronized(new Object()) { //thread-safe, SRSLy? }никакие два потока не могут войти в один и тот же
synchronizedблок с тем же замком. В этом случае (и аналогично в вашем коде) объект блокировки изменяется при каждом выполнении, поэтомуsynchronizedфактически не имеет никакого эффекта.даже если вы использовали конечную переменную (или
this) для синхронизации код по-прежнему неверен. Два потока могут сначала читатьiдоtempсинхронно (имея то же значение локально вtemp), то первый присваивает новое значениеi(скажем, от 1 до 6), а другой делает то же самое (от 1 до 6).синхронизация должна охватывать от чтения до присвоения значения. Ваша первая синхронизация не имеет никакого эффекта (чтение
intявляется атомарным) и второй как что ж. На мой взгляд, это правильные формы:void synchronized incIBy5() { i += 5 } void incIBy5() { synchronized(this) { i += 5 } } void incIBy5() { synchronized(this) { int temp = i; i = temp + 5; } }
объявление переменной как летучие означает, что изменение его значения немедленно влияет на фактическое хранение памяти для переменной. Компилятор не может оптимизировать любые ссылки на переменную. Это гарантирует, что когда один поток изменяет переменную, все другие потоки немедленно видят новое значение. (Это не гарантируется для энергонезависимых переменных.)
объявления atomic переменная гарантирует, что операции, выполненные над переменной происходят атомарным способом, т. е., что все подэтапы операции завершены в пределах потока, они выполняются и не прерываются другими потоками. Например, операция приращения и проверки требует, чтобы переменная была увеличена и затем сравнивалась с другим значением; атомарная операция гарантирует, что оба этих шага будут завершены, как если бы они были одной неделимой/бесперебойной операцией.
синхронизация все доступы к переменная позволяет только одному потоку одновременно обращаться к переменной и заставляет все другие потоки ждать, пока этот поток доступа освободит свой доступ к переменной.
синхронизированный доступ аналогичен атомарному доступу, но атомарные операции обычно реализуются на более низком уровне программирования. Кроме того, вполне возможно синхронизировать только некоторые обращения к переменной и позволить другим обращениям быть несинхронизированными (например, синхронизировать все записи в переменную, но ни один из читает из него).
атомарность, синхронизация и изменчивость являются независимыми атрибутами, но обычно используются в сочетании для обеспечения надлежащего взаимодействия потоков для доступа к переменным.
дополнительное соглашение(апрель 2016 года)
синхронизировать доступ к переменной обычно реализуется с помощью монитор или семафор. Это низкоуровневые мьютекс (взаимного исключения) механизмы, которые позволяют потоку получить контроль над переменной или блоком кода исключительно, заставляя все другие потоки ждать, если они также пытаются получить тот же мьютекс. Как только владеющий поток освобождает мьютекс, другой поток может получить мьютекс в свою очередь.
дополнительное соглашение(июль 2016)
синхронизация происходит на объект. Это означает, что вызов синхронизированного метода класса заблокирует
volatile:
volatile- ключевое слово.volatileзаставляет все потоки получать последнее значение переменной из основной памяти вместо кэша. Для доступа к изменчивым переменным блокировка не требуется. Все потоки могут получить доступ к изменчивому значению переменной одновременно.используя
volatileпеременные уменьшают риск ошибок согласованности памяти, потому что любая запись в переменную volatile устанавливает связь "происходит раньше" с последующие чтения той же переменной.это означает, что изменения в
volatileпеременные всегда видны другим потокам. Более того, это также означает, что когда-нить, читаетvolatileпеременная, она видит не только последние изменения в volatile, но и побочные эффекты кода, которые привели к изменению.когда использовать: один поток изменяет данные, а другие потоки должны читать последнее значение данных. Другие потоки будут принимать некоторые меры, но они не будут обновлять данные.
AtomicXXX:
AtomicXXXклассы поддерживают потокобезопасное Программирование без блокировки на отдельных переменных. ЭтиAtomicXXXклассы (например,AtomicInteger) устраняет ошибки несогласованности памяти / побочные эффекты модификации летучих переменных, которые были доступны в нескольких потоках.когда использовать: несколько потоков могут читать и изменить данные.
синхронизации:
synchronizedключевое слово, используемое для защиты метода или блока кода. Делая метод как синхронизированный имеет два эффекта:
во-первых, это невозможно для двух вызовов
synchronizedметоды на том же объекте для перемежения. Когда один поток выполняетsynchronizedметод для объекта, все остальные потоки, которые вызываютsynchronizedметоды для одного и того же блока объектов (приостановить выполнение) до тех пор, пока первый поток не будет выполнен с объектом.во-вторых, когда a
synchronizedметод завершает работу, он автоматически устанавливает происходит-раньше отношения с любым последующим вызовомsynchronizedметод для того же объекта. Это гарантирует, что изменения состояния объекта будут видны всем потокам.когда использовать: несколько потоков могут читать и изменять данные. Ваша бизнес-логика не только обновляется данные, но также выполняет атомарные операции
AtomicXXXэквивалентноvolatile + synchronizedхотя реализация разная.AmtomicXXXвыходитvolatileпеременные +compareAndSetметоды, но не использовать синхронизацию.связанные вопросы SE:
разница между volatile и synchronized в Java
Volatile boolean vs 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
Я знаю, что два потока не могут войти в блок синхронизации одновременно
два потока не могут войти в синхронизированный блок на одном и том же объекте дважды. Это означает, что два потока могут входить в один и тот же блок на разных объектах. Эта путаница может привести к такому коду.
private Integer i = 0; synchronized(i) { i++; }это не будет вести себя так, как ожидалось, как это может быть блокировка на другой объект каждый раз.
если это правда, то как это атомный.incrementAndGet () работает без синхронизации ?? и является потокобезопасным ??
да. Он не использует блокировку для обеспечения безопасности резьбы.
Если вы хотите знать, как они работают более подробно, вы можете прочитать код для них.
а в чем разница между внутренним чтением и записью в переменную Volatile / Atomic Variable ??
атомарный класс использует volatile поля. нет никакой разницы в поле. Разница заключается в выполняемых операциях. Атомарные классы используют операции CompareAndSwap или CAS.
Я читал в какой-то статье, что поток имеет локальную копию переменных, что это ??
Я могу только предположить, что это относится к тому факту, что каждый процессор имеет свое собственное кэшированное представление памяти, которое может отличаться от любого другого процессора. Чтобы обеспечить согласованное представление данных ЦП, необходимо использовать методы потокобезопасности.
это только проблема, когда память совместно используется по крайней мере один поток обновляет его.
volatile + synchronization-это надежное решение для операции (оператора), которое должно быть полностью атомарным, которое включает в себя несколько инструкций для ЦП.
скажем, например: volatile int i = 2; i++, который является ничем иным, как i = i + 1; что делает i как значение 3 в памяти после выполнения этого оператора. Это включает в себя чтение существующего значения из памяти для i (что равно 2), загрузку в регистр аккумулятора процессора и выполнение вычисления путем увеличения существующего значения с одним (2 + 1 = 3 в аккумуляторе), а затем записать это увеличенное значение обратно в память. Эти операции не являются достаточно атомарными, хотя значение i является изменчивым. я, будучи изменчивым, гарантирует только то, что одно чтение/запись из памяти является атомарным, а не с несколькими. Следовательно, нам нужно синхронизировать также вокруг i++, чтобы он был атомарным утверждением с доказательством дурака. Помните, что оператор включает в себя несколько операторов.
надеюсь, что объяснение понятно достаточно.
Java volatile модификатор является примером специального механизма, гарантирующего, что связь происходит между потоками. Когда один поток записывает в переменную volatile, а другой поток видит эту запись, первый поток сообщает второму обо всем содержимом памяти до тех пор, пока он не выполнит запись в эту переменную volatile.
атомарные операции выполняются в одной единице задачи без вмешательства других операций. Атомарные операции необходимы в многопоточной среде, чтобы избежать несогласованности данных.
Comments