написать впереди
предыдущий постЕсть три источника одновременных ошибок, пожалуйста, следите за ними.говорил о可见性/原子性/有序性Три проблемы, эти проблемы обычно противоречат нашей интуиции и способу мышления, что приводит к множеству одновременных ошибок.
- Для решения короткой платы ЦП, памяти, ввода-вывода, кэш-памяти увеличено, но это приводит к проблемам с видимостью
- Компилятор/процессор
擅自Оптимизация (код Java станет байт-кодом Java после компиляции, байт-код загружается в JVM загрузчиком классов, JVM выполняет байт-код и, наконец, должен быть преобразован в инструкции сборки для выполнения на ЦП), что приводит к проблемам с заказом
Первоначальный замысел хорош, но породил новые проблемы. Самый эффективный способ — запретить кэширование и оптимизацию компиляции. Хотя проблему можно решить, очень неловко «вернуться к исходной точке и стоять перед зеркало." Производительность программы вызывает беспокойство.
решение
- Как программисты, мы не хотим писать ошибки, влияющие на KPI, поэтому мы надеемся, что модель памяти проста для понимания и легко программируется. Это должно основываться наСильная модель памятиписать код
- Поскольку компиляторы и процессоры не хотят, чтобы посторонние говорили, что это медленно обрабатывается, они хотят, чтобы модель памяти была для них как можно менее ограниченной.Оптимизировать без авторизации, что требуетСлабая модель памяти
Как говорится: "Нет ничего, что нельзя было бы решить встречей. Если есть, то открывай заново" 😂
У экспертов JSR-133 есть новая идея: поскольку кэширование и оптимизацию компиляции нельзя полностью запретить, тоПо требованиюОтключите кэширование и оптимизацию компиляции, а также при необходимости добавьте некоторые ограничения, в том числе ограничения, кратко упомянутые в предыдущей статье.изменчивый, синхронизированный, окончательныйТри ключевых слова, наряду с другими, которые вы, возможно, слышалиHappens-BeforeПринципы (включая ограничения на видимость и упорядоченность), правила «происходит до» также являются основным содержанием этой главы.
Чтобы удовлетворить сильные потребности двоих и позаботиться об эмоциях обеих сторон, поэтому: JMM солгал программистам во спасение: «Он будет строго соблюдать правила Happpen-Befores и не будет переупорядочивать», чтобы успокоить программисты, но в частном порядке Имеет свою стратегию:
- Для переупорядочения, которое изменит результат выполнения программы, JMM требует, чтобы компилятор и процессор запрещали такое переупорядочение.
- Для переупорядочения, не меняющего результат выполнения программы, JMM не требует компиляторов и процессоров (JMM допускает такое переупорядочение).
Проиллюстрируем диаграммой:
Это ложь во спасение, хоть это и ложь, но она все же заботится об интересах программистов, так что нам нужно только понимать, что происходит, прежде чем правила будут гарантированы (картинка нарисована давно, я не знаю не знаю, объясняет ли это ложь 😅, добро пожаловать, чтобы оставить сообщение)
Happens-before
Правила «происходит до» в основном используются для ограничения двух операций.Две операции имеют отношение «происходит до», что не означает, что предыдущая операция должна быть выполнена до последней операции.происходит-прежде требует, чтобы предыдущая операция (результат выполнения) была видна следующей операции, (the first is visible to and ordered before the second)
Сказав так много, давайте взглянем на небольшой фрагмент кода, который шаг за шагом проведет вас к принципу Happen-Befores и посмотрим, как решить его с помощью этого принципа.видимостьиупорядоченностьЭта проблема:
class ReorderExample {
int x = 0;
boolean flag = false;
public void writer() {
x = 42; //1
flag = true; //2
}
public void reader() {
if (flag) { //3
System.out.println(x); //4
}
}
}
Предполагая, что поток A выполняет метод записи, а поток B выполняет метод чтения, напечатанный x может быть равен 0, как объяснялось в предыдущей статье: поскольку коды 1 и 2 независимость данныхотношения, поэтому могут быть изменены
flag = true; //2
x = 42; //1
Таким образом, поток A будетflag = trueнаписатьно не переназначая x, поток B мог напечатать x равно 0
Затем попробуйте добавить к флагу ключевое слово volatile:
volatile boolean flag = false;
Даже с добавлением ключевого слова volatile эта проблема не была решена до java1.5, но java1.5 и более поздние версии улучшили семантику volatile., задача решена, что неотделимо от ограничений правила Happens-before, всего правил 6, и см.
правила порядка программы
в потокедля каждой операции происходит перед любыми последующими операциями в этом потоке Первое ощущение состоит в том, что этот принцип является "бессмыслицей" в идеальном состоянии и противоречит упомянутой выше ситуации переупорядочивания. Обратите внимание, что это операция в потоке, которая на самом деле подразумевает семантику "как если бы" : Грубо говоря, до тех пор, пока результат выполнения не изменится, как бы "отсортировано" оно ни было правильно
Это базовое правило, «происходит до» — это многопоточное правило, поэтому оно должно быть ограничено другими правилами, чтобы отразить его порядок, не беспокойтесь, продолжайте смотреть вниз.
правила изменяемой переменной
Запись в изменчивое поле происходит до любого последующего чтения изменчивого поля.
Я добавлю две строки кода к приведенной выше программе для иллюстрации:
public class ReorderExample {
private int x = 0;
private int y = 1;
private volatile boolean flag = false;
public void writer(){
x = 42; //1
y = 50; //2
flag = true; //3
}
public void reader(){
if (flag){ //4
System.out.println("x:" + x); //5
System.out.println("y:" + y); //6
}
}
}
Это включает в себя семантику расширения памяти volatile.Давайте сначала посмотрим на таблицу:
| Можно ли переупорядочить | вторая операция | вторая операция | вторая операция |
|---|---|---|---|
| первое действие | Обычное чтение/запись | изменчивое чтение | изменчивая запись |
| Обычное чтение/запись | - | - | NO |
| изменчивое чтение | NO | NO | NO |
| изменчивая запись | - | NO | NO |
из этой формыпоследний рядКак можно видеть:
Если вторая операция является энергозависимой записью, изменение порядка невозможно независимо от первой операции.Это гарантирует, что операции перед записью в энергозависимую память не будут переупорядочены после записи в энергозависимую память.Возьмем приведенный выше код в качестве примера, код 1 и 2 не будут переупорядочены после кода 3, но код 1 и 2 могут быть переупорядочены (нет зависимостей и не повлияет на результат выполнения), сказано здесь иправила порядка программыОн уже подключен?
из этой формыПредпоследняя строкаКак можно видеть:
Если первая операция является изменчивым чтением, независимо от того, что представляет собой вторая операция, ее нельзя переупорядочить,Это гарантирует, что операции после чтения volatile не будут переупорядочены до чтения volatile.Возьмите приведенный выше код в качестве примера, код 4 предназначен для чтения изменчивой переменной, код 5 и 6 не будут переупорядочены перед кодом 4.
Реализация изменчивой семантики памяти применена к «барьеру памяти», ибо этого достаточно, чтобы написать отдельную главу Здесь, чтобы не заслонять ореол главного героя Бывает-раньше, и сохранять преемственность понимания Бывает- прежде, я не буду объяснять слишком много.
Здесь, глядя на это правило, кажется, что оно не решает никаких проблем, потому что для работы его нужно комбинировать с третьим правилом.
транзитивное правило
Если А происходит раньше В, а В происходит раньше С, то А происходит раньше С. Непосредственно выше, чтобы проиллюстрировать приведенный выше пример
Как видно из приведенного выше рисунка
-
x =42иy = 50Happens-beforeflag = true, ЭтоПравило 1 - запись переменной (код 3)
flag=trueПроисходит перед чтением переменных (код 4)if(flag),ЭтоПравило 2
в соответствии сПравило 3транзитивное правило,x =42Происходит перед чтением переменныхif(flag)
Тайна, которая должна быть раскрыта: если поток B считывает, что флаг истинен, то
x =42иy = 50Он должен быть виден потоку B, что является усовершенствованием Java 1.5 (предыдущая версия может быть переупорядочена с помощью обычной записи переменных и записи переменных переменных).
Обычно вышеперечисленные три правила являются своего рода совместным ограничением.Вы понимаете здесь? Правила еще не закончились, продолжайте читать
Правила блокировки монитора
Отпирание замка происходит до последующего запирания замка
Я думаю, вам должно быть хорошо знакомо это правило, объясняющее ключевое слово synchronized.
public class SynchronizedExample {
private int x = 0;
public void synBlock(){
// 1.加锁
synchronized (SynchronizedExample.class){
x = 1; // 对x赋值
}
// 3.解锁
}
// 1.加锁
public synchronized void synMethod(){
x = 2; // 对x赋值
}
// 3. 解锁
}
Поток, который получает блокировку первым, освобождает блокировку после присвоения значения x, а другой снова получает блокировку, и вы должны иметь возможность видеть изменения в назначении x. Это так просто. Пожалуйста, используйте следующее команда, чтобы просмотреть приведенную выше программу, чтобы увидеть, что блок синхронизации и метод синхронизации изменены В чем разница между преобразованием в инструкции по сборке?
javap -c -v SynchronizedExample
Это связано с семантикой синхронизированного, вы можете сначала понять это сами, а содержание блокировки будет подробно объяснено.
правила запуска()
Если поток A выполняет операцию ThreadB.start() (запуская поток B), то операция ThreadB.start() потока A происходит до любой операции в потоке B, то есть после того, как основной поток A запускает дочерний поток B, Sub -поток B может видеть работу основного потока перед запуском подпотока B, и это можно понять за секунды, взглянув на программу
public class StartExample {
private int x = 0;
private int y = 1;
private boolean flag = false;
public static void main(String[] args) throws InterruptedException {
StartExample startExample = new StartExample();
Thread thread1 = new Thread(startExample::writer, "线程1");
startExample.x = 10;
startExample.y = 20;
startExample.flag = true;
thread1.start();
System.out.println("主线程结束");
}
public void writer(){
System.out.println("x:" + x );
System.out.println("y:" + y );
System.out.println("flag:" + flag );
}
}
результат операции:
主线程结束
x:10
y:20
flag:true
Process finished with exit code 0
Поток 1 видит все результаты присваивания до того, как основной поток вызовет thread1.start(), здесь нет «конца основного потока», знаете почему? Это знание нити демона связано
правила присоединения()
Если поток A выполняет операцию ThreadB.join() и завершается успешно, то любая операция в потоке B происходит до того, как поток A успешно завершится из операции ThreadB.join(),Вопреки начальному правилу, основной поток A ожидает завершения подпотока B, когда подпоток B завершен, основной поток может увидеть операцию присваивания подпотока B, внести небольшое изменение в программу, и вы понять это за секунды
public class JoinExample {
private int x = 0;
private int y = 1;
private boolean flag = false;
public static void main(String[] args) throws InterruptedException {
JoinExample joinExample = new JoinExample();
Thread thread1 = new Thread(joinExample::writer, "线程1");
thread1.start();
thread1.join();
System.out.println("x:" + joinExample.x );
System.out.println("y:" + joinExample.y );
System.out.println("flag:" + joinExample.flag );
System.out.println("主线程结束");
}
public void writer(){
this.x = 100;
this.y = 200;
this.flag = true;
}
}
результат операции:
x:100
y:200
flag:true
主线程结束
Process finished with exit code 0
Слова «конец основного потока» распечатываются, и это все еще имеет какое-то отношение к выходу потока.
Суммировать
- Смысл Happens-before состоит в том, чтобы разрешить, чтобы результат предыдущей операции был виден для следующей операции., я полагаю, что к настоящему моменту вы уже имеете представление о правилах Happens-before, которые решают проблемы видимости и упорядочения многопоточного программирования, но не решили полностью проблему атомарности (кроме синхронизированных)
- Правила запуска и присоединения также являются одним из способов решения проблемы связи между основным потоком и дочерним потоком.
- С точки зрения семантики памяти volatile
写-读с замком释放-获取Имеют одинаковый эффект памяти; энергозависимая запись и снятие блокировки имеют одинаковую семантику памяти; энергозависимое чтение и получение блокировки имеют одинаковую семантику памяти, ⚠️⚠️⚠️ (стук по доске) volatile решает проблему видимости, синхронизация решает проблему Проблема атомарности , что определенно не одно и то же, будет объяснено в следующей статье.
Дополнительная информация
- посетить личный блогdayarch.top/Узнайте больше заранее
- Многопоточный цикл статей будет написан в соответствии с моим ритмом набросков в целом, но если у вас есть вопросы, то добро пожаловать и в мое отдельное заведениеРезюме многопоточной серии вопросов и комментариевОставьте сообщение в статье, и я отвечу в единой форме.Если есть много общих вопросов, я вставлю соответствующие главы, чтобы объяснить отдельно.
- Я также синхронно загружу соответствующий код параллельной статьи в кодовую базу.Официальный аккаунт ответит «демо», щелкнет ссылку и найдет подпроект параллелизма.
- Если статья была вам полезна, нажмите 👍
вопрос души
- В чем разница между синхронизированным блоком и синхронизированным методом при компиляции в инструкции ЦП?
- У потоков есть Daemon (потоки демона) и потоки, не являющиеся демонами.Знаете ли вы политику выхода потоков?
- У вас есть какие-то сомнения по поводу Happens-before?
инструменты повышения производительности
Конструктор форм MarkDown
Многие таблицы в этой статье вклеены с официального сайта, как их сразу конвертировать в MD таблицы? Такwoohoo.tables генератор.com/markdown_he…Это может помочь вам, будь то создание таблицы MD или вставка контента для создания таблицы, и контент, конечно, отличный, не только таблица MD, найдите ее сами, дополнительные инструменты, официальный ответ учетной записи «инструменты», чтобы получить
Рекомендуемое чтение
- Я использую SpringBoot каждый день, но до сих пор не понимаю, как RESTful API возвращает унифицированный формат данных?
- Модель родительского делегирования: часто задаваемые вопросы о крупных фабриках, которые легко решить
- Интервью не знает разницы между BeanFactory и ApplicationContext?
- Как разработать хороший RESTful API
- Красно-черное дерево, подробное объяснение супердинамических и статических диаграмм, простое и понятное
Добро пожаловать, чтобы продолжать обращать внимание на общественный номер: «Сун Гун И Бин».
- Передовая технология Java для обмена галантереей
- Резюме эффективных инструментов | Ответ на «Инструменты»
- Анализ вопроса интервью и ответ
- Сбор технических данных | Ответ на «данные»
Узнайте о стеке технологий Java легко и весело, думая о чтении детективных романов, и постепенно разлагайте технические проблемы, основываясь на принципах упрощения сложных проблем, конкретизации абстрактных проблем и графики.Технология постоянно обновляется, пожалуйста, продолжайте платить внимание...