Оригинальная статья, краткое изложение опыта и жизненные перипетии на всем пути от набора в школу до фабрики А
Нажмите, чтобы узнать подробностиwww.codercc.com
1. Введение в три свойства
Точка входа часто необходима при анализе проблем безопасности потоков в параллельном программировании, то естьдва ядра: абстрактная модель памяти JMM и правила «происходит до» (вэта статьябыло передано), три свойства:Атомарность, порядок и видимость. оsynchronizedиvolatileЭто обсуждалось, и я подумываю поместить эти два артефакта в параллельное программирование вАтомарность, порядок и видимостьДля сравнения, конечно, это тоже высокочастотный тестовый участок в интервью, что стоит отметить.
2. Атомарность
атомарность означаетОперация не прерывается, либо все исполнения успешны, либо все исполнения терпят неудачу, и возникает ощущение «жить и умирать вместе».. Даже когда несколько потоков выполняются вместе, после начала операции она не будет прервана другими потоками. Давайте сначала посмотрим, какие операции являются атомарными, а какие нет, и получим интуитивное представление:
int a = 10; //1
a++; //2
int b=a; //3
a = a+1; //4
Из четырех приведенных выше предложений толькоСуществует первый оператор, который является атомарной операцией, присвоить 10 переменной a рабочей памяти потока, а оператор 2 (a++) фактически включает в себя три операции: 1. прочитать значение переменной a, 2: добавить единицу к a, 3. вычислить Последнее значение затем присваивается переменная а, и эти три операции не могут составлять атомарную операцию. Анализ утверждений 3 и 4 показывает, что эти два утверждения не являются атомарными. Конечно,Java-модель памяти8 операций, определенных в, являются атомарными и неделимыми.
- lock (замок): воздействуя на переменную в основной памяти, идентифицирует переменную как потоко-эксклюзивное состояние;
- Unlock (разблокировать): воздействует на переменную в основной памяти, освобождает переменную в заблокированном состоянии, и освобожденная переменная может быть заблокирована другими потоками.
- read (чтение): переменная, действующая на основную память, которая передает значение переменной из основной памяти в рабочую память потока для использования последующим действием загрузки;
- load (загрузить): воздействует на переменную в рабочей памяти, помещает значение переменной, полученное операцией чтения из основной памяти, в копию переменной в рабочей памяти
- use (использование): воздействует на переменные в рабочей памяти, передает значение переменной в рабочей памяти исполняющему движку, который будет выполняться всякий раз, когда виртуальная машина встречает инструкцию байт-кода, которой необходимо использовать значение переменной;
- assign (назначение): воздействует на переменную в рабочей памяти, присваивает значение, полученное от исполнительного движка, переменной в рабочей памяти, и выполняет эту операцию всякий раз, когда виртуальная машина встречает инструкцию байт-кода, присваивающую значение переменной;
- store (хранение): переменная, действующая на рабочую память, которая передает значение переменной из рабочей памяти в основную память для последующих операций записи;
- запись (операция): переменная, действующая на основную память, которая помещает значение переменной, полученное операцией сохранения из рабочей памяти, в переменную в основной памяти.
Вышеупомянутые командные операции довольно низкоуровневы и могут быть освоены как расширенные знания. Итак, как вы понимаете эти инструкции?Например, чтобы скопировать переменную из основной памяти в рабочую память, вам нужно выполнить операции чтения и загрузки, а для синхронизации рабочей памяти с основной памятью вам нужно выполнить операции сохранения и записи. Пожалуйста, имейте в виду:Модель памяти Java требует только, чтобы две вышеуказанные операции выполнялись последовательно, а не последовательно.. Другими словами, между чтением и загрузкой могут быть вставлены другие инструкции, а между сохранением и записью могут быть вставлены другие инструкции. Например, при доступе к a и b в основной памяти может произойти следующая последовательность операций:read a,read b, load b,load a.
С помощью атомарных операций чтения, загрузки, использования, назначения, хранения, записи вы можетеОбычно считается, что доступ для чтения и записи основных типов данных является атомарным.(Исключением является неатомарное соглашение Long и Double)
synchronized
Выше приведено в общей сложности восемь атомарных операций, шесть из которых могут удовлетворять атомарности доступа и чтения и записи основных типов данных, и есть две атомарные операции блокировки и разблокировки. Если нам нужен более широкий спектр атомарных операций, мы можем использовать блокировку и разблокировку атомарных операций. Хотя jvm не открывает для нас блокировку и разблокировку, jvm открыт для нас с инструкциями более высокого уровня, инструкциями monitorenter и monitorexit, которые отражаются в коде java как ключевое слово ---synchronized, то естьсинхронизированный удовлетворяет атомарности.
изменчивый Давайте сначала рассмотрим такой пример:
public class VolatileExample {
private static volatile int counter = 0;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++)
counter++;
}
});
thread.start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(counter);
}
}
Откройте 10 потоков, каждый поток добавляет 10000 раз, если нет проблем с безопасностью потоков, окончательный результат должен быть: 10*10000 = 100000; но результат многократного выполнения меньше 100000, проблема в следующем.volatile не гарантирует атомарность, я уже говорил, что counter++ не является атомарной операцией, она состоит из трех шагов: 1. Прочитать значение переменной counter 2. Прибавить единицу к счетчику 3. Присвоить новое значение переменной counter. Если поток A считывает счетчик в рабочую память, а другие потоки выполнили операцию автоматического увеличения этого значения, то значение потока A, естественно, является значением с истекшим сроком действия, поэтому общий результат должен быть меньше 100000.
Если volatile гарантирует атомарность, должны быть соблюдены следующие два правила:
- Результат операции не зависит от текущего значения переменной или может гарантировать, что только один поток изменяет значение переменной;
- Переменные не должны участвовать в инвариантных ограничениях с другими переменными состояния.
3. Порядок
synchronized
Синхронизированная семантика означает, что блокировка может быть получена только одним потоком одновременно, и когда блокировка занята, другие потоки могут только ждать. Следовательно, синхронизированная семантика требует, чтобы потоки могли выполняться только «последовательно» при доступе к общим переменным для чтения и записи, поэтомусинхронизированный заказан.
volatile
Как упоминалось в модели памяти Java, для оптимизации производительности компилятор и процессор переупорядочивают инструкции, то есть естественное упорядочение программ Java можно резюмировать следующим образом:Если наблюдается в этом потоке, все операции упорядочены, если один поток наблюдается в другом потоке, все операции не по порядку. Существует способ блокировки с двойной проверкой (Double-checked Locking) в реализации шаблона singleton. код показывает, как показано ниже:
public class Singleton {
private Singleton() { }
private volatile static Singleton instance;
public Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
Зачем добавлять сюда volatile? Давайте сначала проанализируем ситуацию без volatile.Проблема заключается в следующем:
instance = new Singleton();
Этот оператор фактически содержит три операции: 1. Выделить пространство памяти объекта, 2. Инициализировать объект, 3. Установить экземпляр так, чтобы он указывал на только что выделенный адрес памяти. Однако из-за проблем с переупорядочением возможен следующий порядок выполнения:
Если 2 и 3 переупорядочены, поток B будет истинным при оценке (instance==null), но на самом деле экземпляр не инициализирован успешно, очевидно, что последующая операция для потока B будет неправильной. иукрашен летучимиВы можете отключить переупорядочивание 2 и 3 операций, чтобы избежать этого.volatile содержит семантику, запрещающую переупорядочивание инструкций,.
4. Видимость
Видимость означает, что когда поток изменяет общую переменную, другие потоки могут немедленно узнать об изменении. через предыдущийsynchronzedАнализируется семантика памяти: когда поток получает блокировку, он получает последнее значение разделяемой переменной из основной памяти, а когда блокировка снимается, разделяемая переменная синхронизируется с основной памятью. тем самым,синхронизированный имеет видимость. то же самое влетучий анализ, добавив в директивуинструкция блокировки, для видимости памяти. следовательно,volatile имеет видимость
5. Резюме
В этой статье я в основном сравниваю три свойства синхронизированного и изменчивого: атомарность, видимость и упорядоченность, которые резюмируются следующим образом:
синхронизировано: атомарность, порядок и видимость;volatile: имеет порядок и видимость
использованная литература
Искусство параллельного программирования на Java
"Глубокое понимание виртуальной машины Java"