Предисловие
Иногда, если вы используете синхронизацию, просто для чтения и написания одного или двух полей экземпляра, это кажется слишком дорогим. Ключевое слово «летучие» обеспечивает механизм без блокировки для синхронного доступа к полям экземпляра. Если домен объявлен как нестабильный, компилятор и виртуальная машина знают, что домен может быть обновлен одновременно другим потоком. Прежде чем говорить о нестабильном ключевом словом, нам необходимо понять соответствующие концепции модели памяти и три характеристики в одновременном программировании: атомность, видимость и упорядоченность.
1. Модель памяти Java с атомностью, видимостью и упорядоченностью
Модель памяти Java предусматривает, что все переменные существуют в основной памяти, и каждый поток имеет свою собственную рабочую память. Все операции потока на переменной должны выполняться в рабочей памяти и не могут напрямую работать в основной памяти. И каждый поток не может получить доступ к рабочей памяти других потоков.
В Java выполните следующее оператор:
int i = 3;
Поток выполнения должен сначала назначить линию кэша, где переменная, которую я находится в своем собственном рабочем потоке, а затем написать ее в основную память. Вместо того, чтобы писать значение 3 непосредственно в основную память.
Итак, какие гарантии сама язык Java обеспечивает атомность, видимость и упорядоченность?
Атомность
Операции чтения и назначения переменных основных типов данных являются атомными операциями, то есть эти операции не могут быть прерваны и выполнены или выполнены или нет.
Давайте посмотрим на следующий код:
x = 10; // оператор 1y = x; // оператор 2x ++; // оператор 3x = x + 1; // оператор 4
Только утверждение 1 является атомной операцией, и ни одно из трех других утверждений не является атомным операциями.
Заявление 2 фактически содержит 2 операции. Сначала необходимо прочитать значение x, а затем написать значение x в рабочую память. Хотя две операции чтения значения x и написания значения X в рабочей памяти являются атомными операциями, они не являются атомными операциями вместе.
Точно так же x ++ и x = x+1 включают 3 операции: прочитайте значение x, выполните операцию добавления 1 и напишите новое значение.
Другими словами, только простое чтение и назначение (и число должно быть назначено переменной, а взаимное назначение между переменными не является атомной операцией) является атомной операцией.
В java.util.concurrent.concurrent.concurrent.concurrent.concurrent.concurrent.concurrent, в которых используются очень эффективные инструкции на уровне машин (а не блокировки), чтобы обеспечить атомичность других операций. Например, класс AtomicInteger предоставляет методы IncrementAndget и DecrementAndGet, которые соответственно увеличивают и уменьшают целое число атомно. Класс AtomicInteger может быть безопасно использован в качестве общего счетчика без синхронизации.
Кроме того, этот пакет также содержит атомные классы, такие как Atomicboolean, Atomiclong и AtomicReference для системных программистов, которые разрабатывают только параллельные инструменты, а прикладные программисты не должны использовать эти классы.
Видимость
Видимость относится к видимости между потоками, а измененное состояние одного потока видно в другой потоке. Это результат модификации потока. Другая ветка будет замечена немедленно.
Когда общая переменная изменяется волатильной, она гарантирует, что модифицированное значение будет немедленно обновлено до основной памяти, поэтому оно видно для других потоков. Когда другие потоки должны прочитать, он будет читать новое значение в памяти.
Тем не менее, обычные общие переменные не могут гарантировать видимость, потому что это неясно, когда нормальная общая переменная записывается в основную память после ее изменения. Когда другие потоки читают его, исходное старое значение все еще может быть в памяти, поэтому видимость не может быть гарантирована.
Порядок
В модели памяти Java компиляторам и процессорам разрешается переупорядочить инструкции, но процесс повторного порядка не повлияет на выполнение однопоточных программ, но повлияет на правильность многопоточного одновременного выполнения.
Ключевое слово «летучих» можно использовать для обеспечения определенной «Lines Line». Кроме того, синхронизированный и блокировка могут быть использованы для обеспечения порядка. Очевидно, что синхронизированный и блокировка убедитесь, что существует поток, который выполняет код синхронизации в каждый момент, что эквивалентно разрешению потоков выполнять код синхронизации в последовательности, что естественным образом обеспечивает порядок.
2. летучие ключевые слова
После того, как общая переменная (переменные члена класса, класс статические переменные элемента) изменяется летучей, она имеет два уровня семантики:
Давайте сначала посмотрим на кусок кода. Если поток 1 выполняется первым, а поток 2 выполняется позже:
// потока 1Boolean Stop = false; while (! Stop) {dosomething ();} // потока 2stop = true; Многие люди могут использовать этот метод разметки при прерывании потоков. Но на самом деле этот код работает полностью правильно? Будет ли поток прерван? Не обязательно. Возможно, большую часть времени этот код может прерывать потоки, но он также может привести к прерыванию поток (хотя эта возможность очень мала, как только это произойдет, это приведет к мертвому петлю).
Почему можно вызвать неспособность потока прерывать? Каждый поток имеет свою собственную рабочую память во время процесса работы. При запуске потока 1 он скопирует значение переменной Stop и поместит ее в собственную рабочую память. Затем, когда поток 2 изменяет значение переменной остановки, но у меня не было времени, чтобы написать ее в основную память, поток 2 делает другие вещи, тогда поток 1 не знает об изменениях потока 2 в переменной остановки, поэтому он будет продолжать цикл.
Но после изменения с летучим он становится другим:
Волатильная гарантия атомства?
Мы знаем, что летучие ключевые слова обеспечивает видимость операций, но может ли летучие гарантировать, что операции на переменных являются атомными?
Общественный тест класса {public volatile int inc = 0; public void увеличение () {inc ++; } public static void main (string [] args) {окончательный тест теста = new Test (); for (int i = 0; i <10; i ++) {new Thread () {public void run () {for (int j = 0; j <1000; j ++) test.increase (); }; }.начинать(); } // Убедитесь, что предыдущие потоки завершили выполнение, в то время как (Thread.ActiveCount ()> 1) Thread.yield (); System.out.println (test.inc); }} Результат этого кода не соответствует каждый раз, когда он работает. Это число менее 10000. Как упоминалось ранее, операция автоматического размещения не является атомной. Он включает в себя чтение исходного значения переменной, выполнение дополнительной 1 операции и написание рабочей памяти. То есть три подъездных операции операции самостояния могут быть выполнены отдельно.
Если значение переменной INC составляет 10 в определенное время, поток 1 выполняет операцию самостоятельного введения на переменной, поток 1 сначала считывает исходное значение переменной inc, а затем поток 1 блокируется; Затем поток 2 выполняет операцию самостоятельной инкентации на переменной, а поток 2 также считывает исходное значение переменной вкл. Поскольку поток 1 выполняет только операцию чтения на переменной INC и не изменяет переменную, он не приведет к недействительной линии кэша переменной Cache Inc в потоке 2 в рабочей памяти, поэтому поток 2 будет напрямую перейти к основной памяти, чтобы считать значение INC. Когда обнаруживается, что значение INC составляет 10, затем выполняет увеличение на 1 и записывает 11 в рабочую память и, наконец, записывает ее в основную память. Затем резьба 1 затем выполняет операцию добавления. Поскольку значение INC было прочитано, обратите внимание, что значение INC в потоке 1 по -прежнему 10 в настоящее время, поэтому после потока 1 добавлен Inc, значение INC составляет 11, затем пишет 11 для рабочей памяти и, наконец, записывает ее в основную память. Затем после того, как два потока выполняют операцию самостоятельной реализации, INC увеличивается только на 1.
Операция автоинсюрмана не является атомной работой, и летучая часть не может гарантировать, что любая операция на переменной является атомной.
Может ли нестабильный обеспечить упорядоченность?
Как упоминалось ранее, нестабильное ключевое слово может запретить переупорядочение инструкций, поэтому летучие могут обеспечить порядок в определенной степени.
Есть два значения запрещенных переупорядочения изменчивых ключевых слов:
3. Правильно используйте нестабильное ключевое слово
Синхронизированное ключевое слово предотвращает одновременно несколько потоков из выполнения куска кода, что значительно повлияет на эффективность выполнения программы. Производительность нестабильного ключевого слова лучше, чем в некоторых случаях синхронизированное. Тем не менее, следует отметить, что летучие ключевое слово не может заменить синхронизированное ключевое слово, потому что летучие ключевое слово не может гарантировать атомичность операции. Вообще говоря, при использовании летучих: следующие два условия должны быть выполнены:
Первое условие состоит в том, что это не может быть таким операциями, как самообладание и самообъектив. Как упомянуто выше, летучая часть не гарантирует атомность.
Давайте приведем пример этого. Он содержит инвариант: нижняя граница всегда меньше или равна верхней границе.
Общедоступный номер класса {private volatile int lower, Upper; public int getLower () {вернуть ниже; } public int getUpper () {return opper; } public void setLower (int value) {if (значение> верхний) бросить new allogalargumentException (...); ниже = значение; } public void setupper (int value) {if (значение <нижнее) бросить new allogalargumentException (...); Верхний = значение; }} Таким образом, ограничивает переменные состояния обретения, поэтому определение нижних и верхних полей в качестве летучих типов не полностью реализует безопасность потока класса и, следовательно, все еще требует синхронизации. В противном случае, если два потока выполняют Setlower и Setupper с непоследовательными значениями одновременно, диапазон будет несовместимым. Например, если исходное состояние составляет (0, 5), в то же время, потоковая передача вызовов Setlower (4) и потока B вызывает Setupper (3), очевидно, что значения, хранящиеся в перекрестном хранилище этими двумя операциями, не соответствуют условиям, тогда оба потока пройдут проверку, чтобы защитить инваариант, так что значение последнего диапазона (4, 3), что, очевидно, неверно.
Фактически, это должно обеспечить атомичность операции для использования летучим. Есть два основных сценария для использования летучих:
Флаги статуса
volatile boolean shutdownRequested; ... public void werlownd () {shutdownRequested = true; } public void dowork () {while (! shutdownRequest) {// do Stuff}}Вполне вероятно, что метод Shutdown () будет вызван из -за пределов цикла, то есть в другом потоке, поэтому необходимо выполнить какой -то вид синхронизации, чтобы убедиться, что видимость выключенной переменной правильно реализована. Тем не менее, написание цикла с синхронизированными блоками гораздо более хлопотно, чем написание с флагами летучих состояний. Поскольку летучие упрощает кодирование, а флаги состояния не зависят от какого -либо другого состояния в программе, оно очень подходит для летучих здесь.
Режим двойной проверки (DCL)
Public Class Singleton {Private volatile Static Singleton Extance = null; public static singleton getInstance () {if (ancess == null) {synchronized (this) {if (encement == null) {ences = new singleton (); }} return Encement; }} Использование летучих здесь будет более или менее влиять на производительность, но, учитывая правильность программы, она все еще стоит пожертвовать этим исполнением.
Преимущество DCL заключается в том, что он имеет высокое использование ресурсов. Объекты Singleton создаются только тогда, когда GetInstance выполняется в первый раз, что очень эффективно. Недостатком является то, что реакция немного медленнее при первой загрузке, и в средах высокой параллелистики существуют определенные дефекты, хотя вероятность возникновения очень мала.
Хотя DCL решает проблемы потребления ресурсов, ненужную синхронизацию, безопасность потоков и т. Д. В определенной степени у него все еще есть проблемы с неудачей в некоторых случаях, то есть сбой DCL. В книге «Практика программирования параллельного процесса Java» она рекомендует использовать следующий код (статический внутренний класс синглтонский шаблон) для замены DCL:
открытый класс Singleton {private singleton () {} public static singleton getInstance () {return singletonholder.sinstance; } Частный статический класс Singletonholder {Private Static Final Singleton sinstance = new Singleton (); }} О двойной проверке вы можете просмотреть
4. Резюме
По сравнению с замками, летучая переменная является очень простой, но в то же время очень хрупкий механизм синхронизации, который в некоторых случаях обеспечит лучшую производительность и масштабируемость, чем замки. Если вы строго следите за условиями использования летучих, которые действительно не зависят от других переменных и их собственных предыдущих значений, вы можете использовать летучую вместо синхронизации для упрощения кода в некоторых случаях. Тем не менее, код, использующий летучую, часто более подвержен ошибкам, чем код, используя блокировки. В этой статье представлены два наиболее распространенных вариантов использования, где вместо синхронизации можно использовать летучие. В других случаях нам лучше использовать синхронизированный.
Выше приведено в этой статье, я надеюсь, что это будет полезно для каждого обучения.