Волатильная переменная на языке Java может рассматриваться как «более легкая синхронизированная»; По сравнению с синхронизированным блоком, летучая переменная требует меньше кодировки и накладных расходов времени выполнения, но функциональность, которую она может достичь, является лишь частью синхронизации.
Замок
Замки предоставляют две основные особенности: взаимное исключение и видимость.
Нестабильные переменные
Волатильная переменная имеет характеристики видимости синхронизации, но не имеет атомных характеристик. Это означает, что поток может автоматически обнаружить последнее значение нестабильной переменной.
Волатильные переменные могут использоваться для обеспечения безопасности потока, но могут быть применены только к очень ограниченному набору вариантов использования: нет никаких ограничений между несколькими переменными или между текущим значением переменной и измененным значением. Следовательно, использования одному летучим одному недостаточно для реализации счетчиков, мутекс или любого класса с инвариантами, связанными с несколькими переменными (например, «start <= end»).
Для простоты или масштабируемости вы можете использовать летучие переменные вместо замков. Некоторые идиомы легче кодировать и считывать при использовании летучих переменных вместо замков. Кроме того, летучая переменная не вызывает блокировки резьбы, как блокировка, и, следовательно, редко вызывает проблемы масштабируемости. В некоторых случаях летучая переменная также может обеспечить преимущества производительности по сравнению с замками, если операция чтения намного больше, чем операция записи.
Условия для правильного использования летучих переменных
Вы можете использовать летучую переменную только вместо замков в ограниченных случаях. Чтобы сделать летучую переменную идеальную безопасность резьбы, должны быть выполнены следующие два условия одновременно:
Операции записи в переменные не зависят от текущего значения.
Эта переменная не включена в инвариант с другими переменными.
Фактически, эти условия указывают на то, что эти допустимые значения, которые могут быть записаны в летучую переменную, не зависят от состояния любой программы, включая текущее состояние переменной.
Первые ограничения условия предотвращают использование летучих переменной в качестве противоречивого счетчика. Хотя инкрементная операция (x ++) выглядит как отдельная операция, на самом деле это комбинированная операция, состоящая из последовательности операций с модификацией чтения-модификации, которые должны выполняться атомно, и летучие не могут обеспечить необходимые атомные свойства. Реализация правильной операции требует сохранения значения x постоянной во время операции, что невозможно при летучей переменной. (Однако, если значение корректируется, чтобы записаться только из одного потока, первое условие можно игнорировать.)
Большинство ситуаций программирования вступают в противоречие с одним из этих двух условий, что делает летучую переменную не так универсально применимой к безопасности потока, как синхронизированная. В листинге 1 показан безболезный класс численного диапазона. Он содержит инвариант - нижняя граница всегда меньше или равна верхней границе.
Привести пример
Давайте посмотрим пример ниже. Мы реализуем счетчик. Каждый раз, когда начнется поток, метод счетчика intc будет вызоваться, чтобы добавить счетчик в среду выполнения - JDK версия: JDK1.6.0_31, память: 3G ЦП: x86 2.4g
счетчик открытого класса {public static int count = 0; public static void inc () {// задержка здесь составляет 1 миллисекунду, что делает результат очевидным try {thread.sleep (1); } catch (прерывание Exception e) {} count ++; } public static void main (string [] args) {// запустить 1000 потоков одновременно для выполнения вычислений i ++ и см. Фактический результат для (int i = 0; i <1000; i ++) {new runnable (new Runnable () {@Override public void run () {count.inc ();}}). start (); } // Значение каждого запуска здесь может быть различным, может быть, 1000 System.out.println («Запустить результат: counter.count =" + counter.count); }} Результат работы: counter.count = 995
Фактический результат работы может быть разным каждый раз. Результатом машины является: Результат работы: counter.count = 995. Можно видеть, что в многопоточной среде счет не ожидает, что результат будет 1000.
Многие люди думают, что это проблема параллелизма. Вам нужно только добавить летучие до счета переменной, чтобы избежать этой проблемы. Затем мы изменяем код, чтобы увидеть, соответствует ли результат наши ожидания.
Общественный счетчик класса {public volatile Static int count = 0; public static void inc () {// задержка здесь составляет 1 миллисекунду, что делает результат очевидным try {thread.sleep (1); } catch (прерывание Exception e) {} count ++; } public static void main (string [] args) {// запустить 1000 потоков одновременно, выполните расчеты i ++ и см. Фактический результат для (int i = 0; i <1000; i ++) {new Thread (new Runnable () {@Override public void run () {contre.inc ();}). start (); } // Значение каждого запуска здесь может быть различным, возможно, 1000 System.out.println ("Запустить результат: counter.count =" + counter.count); }}Результат работы: counter.count = 992
Результат работы по -прежнему не составляет 1000, как мы ожидали. Давайте проанализируем приведенные ниже причины
В статье сбора мусора Java описывается распределение памяти в момент JVM. Одной из областей памяти является стек виртуальных машин JVM. Каждый поток имеет стек потоков при запуске, а стек потоков сохраняет информацию о значении переменной во время запуска потока. Когда поток обращается к значению определенного объекта, он сначала находит значение переменной, соответствующей памяти кучи через ссылку объекта, а затем загружает конкретное значение переменной памяти кучи в локальную память потока для создания копии переменной. После этого поток больше не имеет никаких отношений со значением переменной памяти объекта, но непосредственно изменяет значение переменной копии и в определенный момент после модификации (до того, как поток выходит), он автоматически записывает значение переменной потока обратно обратно в переменную кучи объекта. Таким образом, значение объекта в куче изменится. На следующей картине описывается взаимодействие этого письма
EAD и загрузка копии переменных из основной памяти в текущую рабочую память
Используйте и назначьте код выполнения для изменения значения общей переменной
Хранить и написать обновление основного контента, связанного с памятью с данными рабочей памяти
где использование и назначение может появиться несколько раз
Тем не менее, эти операции не являются атомными, то есть после нагрузки чтения, если основная переменная числа памяти изменена, значение в рабочей памяти потока не вызовет соответствующие изменения, поскольку она была загружена, поэтому рассчитываемый результат будет отличаться от ожидаемого.
Для переменных, модифицированных летучими, виртуальная машина JVM гарантирует, что значение, загруженное из основной памяти в потоку рабочей памяти, является последним
Например, если поток 1 и поток 2 выполняют операции чтения и загрузки, и обнаружите, что значение счета в основной памяти составляет 5, то последнее значение будет загружено
После того, как количество кучи изменяется в потоке 1, он будет записан в основную память, а переменная счета в основной памяти станет 6.
Поскольку поток 2 уже выполнил операцию чтения и загрузки, значение переменной основного количества памяти также будет обновлено до 6 после операции.
Это приводит к тому, что параллелизм возникает после того, как два потока изменяются с помощью летучивого ключевого слова во времени.