В параллельной обработке потоков Java в настоящее время существует большая путаница в отношении использования ключевого слова Летучий. Считается, что с использованием этого ключевого слова все будет хорошо при выполнении многопоточной параллельной обработки.
Язык Java поддерживает многопоточность. Для решения проблемы параллельного выполнения потоков внутри языка введены механизмы синхронизированных блоков и изменчивых ключевых слов.
синхронизированный
Все знакомы с синхронизированными блоками, которые реализуются с помощью ключевого слова Synchronized. С добавлением операторов Synchronized и Block при доступе нескольких потоков только один поток может одновременно использовать синхронизированные модифицированные методы или блоки кода.
изменчивый
Для переменных, модифицированных с помощью voluty, каждый раз, когда поток использует переменную, он считывает измененное значение переменной. Volatile можно легко использовать неправильно для выполнения атомарных операций.
Давайте рассмотрим пример ниже: мы реализуем счетчик. Каждый раз при запуске потока будет вызываться метод counter inc, чтобы добавить единицу к счетчику.
Среда выполнения — версия jdk: jdk1.6.0_31, память: 3G, процессор: x86 2.4G
Скопируйте код кода следующим образом:
Счетчик публичного класса {
общедоступный статический счетчик int = 0;
общественная статическая сила Inc() {
//Здесь задержка на 1 миллисекунду, чтобы результат был очевиден
пытаться {
Thread.sleep(1);
} catch (InterruptedException e) {
}
считать++;
}
public static void main(String[] args) {
//Запускаем 1000 потоков одновременно, чтобы выполнить вычисления i++ и увидеть фактические результаты
для (int я = 0; я <1000; я++) {
новый поток (новый Runnable () {
@Override
общественный недействительный запуск () {
Счетчик.вкл();
}
}).начинать();
}
//Значение здесь может быть разным при каждом запуске, возможно, 1000
System.out.println("Результат выполнения: Counter.count=" + Counter.count);
}
}
Результат выполнения: Counter.count=995.
Фактический результат операции каждый раз может быть разным. Результат этой машины: результат выполнения: Counter.count=995. Видно, что в многопоточной среде Counter.count не ожидает, что результат будет равен 1000.
Многие думают, что это проблема многопоточного параллелизма. Чтобы избежать этой проблемы, вам нужно всего лишь добавить переменную перед счетчиком переменных. Затем мы изменим код, чтобы увидеть, соответствует ли результат нашим ожиданиям.
Скопируйте код кода следующим образом:
Счетчик публичного класса {
общедоступный изменчивый статический счетчик int = 0;
общественная статическая сила Inc() {
//Здесь задержка на 1 миллисекунду, чтобы результат был очевиден
пытаться {
Thread.sleep(1);
} catch (InterruptedException e) {
}
считать++;
}
public static void main(String[] args) {
//Запускаем 1000 потоков одновременно, чтобы выполнить вычисления i++ и увидеть фактические результаты
для (int я = 0; я <1000; я++) {
новый поток (новый Runnable () {
@Override
общественный недействительный запуск () {
Счетчик.вкл();
}
}).начинать();
}
//Значение здесь может быть разным при каждом запуске, возможно, 1000
System.out.println("Результат выполнения: Counter.count=" + Counter.count);
}
}
Результат выполнения: Counter.count=992.
Текущий результат все еще не равен 1000, как мы ожидали. Давайте проанализируем причины ниже.
В статье «Сборка мусора Java» описывается распределение памяти во время выполнения JVM. Одной из областей памяти является стек виртуальной машины jvm. Каждый поток имеет стек потоков во время его работы. Стек потоков хранит информацию о значениях переменных во время работы потока. Когда поток обращается к значению объекта, он сначала находит значение переменной, соответствующей памяти кучи, через ссылку на объект, а затем загружает конкретное значение переменной памяти кучи в локальную память потока, чтобы создать копию После этого поток не имеет никакого отношения к значению переменной объекта в куче памяти. Вместо этого он напрямую изменяет значение переменной копирования. В определенный момент после модификации (до выхода потока). значение копии переменной потока автоматически записывается обратно в переменную объекта в куче. Таким образом, изменяется значение объекта в куче. Изображение ниже
Опишите это взаимодействие
чтение и загрузка копирует переменные из основной памяти в текущую рабочую память
использовать и назначать код выполнения и изменять значения общих переменных
хранить и записывать, обновлять содержимое, связанное с основной памятью, с данными рабочей памяти
где использование и назначение могут появляться несколько раз
Однако эти операции не являются атомарными. То есть после загрузки чтения, если переменная счетчика основной памяти изменена, значение в рабочей памяти потока не изменится, поскольку она была загружена, поэтому вычисленный результат будет таким, как ожидалось. одинаковый
Для изменчивых переменных виртуальная машина JVM гарантирует только то, что значение, загружаемое из основной памяти в рабочую память потока, является самым последним.
Например, если поток 1 и поток 2 обнаружат, что значение счетчика в основной памяти равно 5 во время операций чтения и загрузки, они загрузят последнее значение.
После изменения счетчика кучи потока 1 он будет записан в основную память, а переменная count в основной памяти станет равной 6.
Поскольку поток 2 уже выполнил операции чтения и загрузки, после выполнения операции он также обновит значение переменной счетчика основной памяти до 6.
В результате после того, как два потока будут изменены во времени с использованием ключевого слова Летучие, параллелизм все равно будет.