Предисловие
Любой, кто знаком с одновременным программированием Java, знает, что правило происшествия (HB) в JMM (модель памяти Java), которое определяет упорядоченность и видимость многопоточных операций Java, предотвращая влияние повторного порядок компилятора на результаты программы.
На языке Java существует правило «когда раньше». Это частичное соотношение порядок между двумя операциями, определенными в модели памяти Java. Если операция A происходит в первую очередь в операции B, это означает, что до начала операции B влияние операции A может наблюдаться с помощью операции B. «Влияние» включает в себя изменение значения общих переменных в памяти, отправка сообщений, методов вызова и т. Д., Который в основном не имеет отношения к последовательности возникновения во времени. Этот принцип особенно важен. Это главная основа для оценки того, есть ли конкуренция в данных и безопасными тем.
Согласно официальному заявлению:
Когда переменная читается несколькими потоками и записывается по крайней мере одним потоком, если между операциями чтения и записи не существует взаимосвязи HB, возникают проблемы с расой данных.
Чтобы гарантировать, что поток, который управляет B, видит результат операции A (независимо от того, находятся ли A и B в одном и том же потоке), принцип HB должен быть выполнен между A и B, а если нет, это может привести к повторному заказу.
Когда отношения HB отсутствуют, могут возникнуть проблемы повторного порядка.
Каковы правила для HB?
Все очень хорошо знакомы с этим. Большинство книг и статей будут представлены. Давайте кратко рассмотрим это:
Среди них я живет правила доставки, что имеет решающее значение. Как хорошо использовать правила доставки является ключом к достижению синхронизации.
Затем объясните HB с другой точки зрения: когда операция A HB работает B, то результат работы операции A на общей переменной видна для операции B.
В то же время, если операция B HB работает C, то результат работы операции A на общей переменной видна для операции B.
Принцип достижения видимости - протокол кеша и барьера памяти. Видимость достигается с помощью протоколов когерентности кэша и барьеров памяти.
Как достичь синхронизации?
В книге Дуга Леа "Ява параллелизм на практике", следующее описание:
В книге упоминается: путем объединения некоторых правил HB может быть достигнута видимость разблокированной защищенной переменной.
Но поскольку этот метод чувствителен к порядку утверждений, он подвержен ошибкам.
Затем автор продемонстрирует, как синхронизировать переменную с помощью летучих правил и правил программного заказа.
Давайте давайте привычный пример:
Class ThreadprintDemo {static int num = 0; Статический летучий логический флаг = false; public static void main (string [] args) {Thread t1 = new Thread (() -> {for (; 100> num;) {if (! flag && (num == 0 || ++ num % 2 == 0)) {System.out.println (num); flag = true;}}}); Поток t2 = new Thread (() -> {for (; 100> num;) {if (flag && (++ num % 2! = 0)) {System.out.println (num); flag = false;}}}); t1.start (); t2.start (); }}Цель этого кода - распечатать номера 0 - 100 между двумя потоками.
Студенты, знакомые с одновременным программированием, должны сказать, что эта числовая переменная не использует летучие, и будут проблемы с видимостью, то есть поток T1 обновил NUM, и поток T2 не может его воспринимать.
Ха -ха, автор думал в начале, но недавно, изучая правила HB, я обнаружил, что можно удалить летучую модификацию NUM.
Давайте проанализируем это, и плакат нарисовал картину:
Давайте проанализируем этот рисунок:
ПРИМЕЧАНИЕ. Правило HB гарантирует, что результаты предыдущей операции видны для следующей операции.
Следовательно, в приведенном выше апплете поток B полностью осведомлен о модификации NUM по потоку A - даже если NUM не модифицируется с помощью летучих.
Таким образом, мы используем принцип HB для реализации синхронной работы переменной, то есть в многопоточной среде, мы обеспечиваем безопасность одновременной модификации общих переменных. И нет никаких примитивов Java для этой переменной: летучие и синхронизированные и CAS (при условии, что она имеет значение).
Это может показаться небезопасным (на самом деле безопасным) и может показаться нелегким для понимания. Потому что все это реализовано протоколом кэша и барьером памяти в базовом HB.
Другие правила достижения синхронизации
Реализация с использованием правил завершения потока:
статический int a = 1; public static void main (string [] args) {thread tb = new Thread (() -> {a = 2;}); Thread ta = new Thread (() -> {try {tb.join ();} catch (прерывание Exception e) {// no} System.out.println (a);}); ta.start (); TB.Start (); } Используйте правила начала потока для реализации:
статический int a = 1; public static void main (string [] args) {thread tb = new Thread (() -> {System.out.println (a);}); Thread TA = New Thread (() -> {TB.Start (); a = 2;}); ta.start (); }Эти две операции также могут обеспечить видимость переменной a.
Это действительно подрывает предыдущую концепцию. В предыдущей концепции, если переменная не модифицирована летучими или окончательными, то ее чтение и письмо при многопоточном чтении определенно небезопасны - потому что будут кэши, что приведет к чтению которых не является последним.
Однако, используя HB, мы можем достичь этого.
Суммировать
Хотя название этой статьи состоит в том, чтобы реализовать синхронные операции общих переменных до происшествий, главная цель-понять, прежде чем более глубоко. Понимание его случая, потому что концепция на самом деле заключается в том, чтобы обеспечить упорядоченность предыдущей операции к следующей операции, а видимость операции приводит к многопоточной среде.
В то же время, гибко используя переходные правила, а затем объединяя правила, можно синхронизировать два потока - реализация указанной общей переменной без использования примитивов также может обеспечить видимость. Хотя это, кажется, не очень легко читать, это также попытка.
Даг Ли дает практику в JUC о том, как объединить правила для достижения синхронизации.
Например, внутренний класс синхронизировал старую версию FutureTask (исчез), модифицируют летучую переменную с помощью метода TryReleaseSeared, а TryAcquireshared считывает летучую переменную, которая использует летучие правила;
Это использует преимущества правил заказа программы, установив нелетую переменную результата, прежде чем TryReLeaseSeared, а затем считывая переменную результата после TryAcquireShared.
Это обеспечивает видимость переменной результата. Аналогично нашему первому примеру: использование правил заказа программ и летучих правил для достижения нормальной видимости переменной.
Сам Даг Ли сказал, что эта технология «использование помощи» очень подвержена ошибкам и должна использоваться с осторожностью. Но в некоторых случаях этот вид «рычага» очень разумный.
На самом деле, Blockqueueue также «используется», которые происходят перед правилами. Помните правило разблокировки? Когда происходит разблокировка, внутренние элементы должны быть видны.
В библиотеке классов есть и другие операции, которые также «используют» принцип происшествия, прежде всего: одновременные контейнеры, обратный отсчет, семафор, будущее, исполнитель, циклический барьер, обменник и т. Д.
Короче говоря:
Принцип происшествия-это ядро JMM. Только когда принцип HB будет выполнен заказать, а видимость будет обеспечена, в противном случае компилятор переупорядочивает код. HB даже определяет правила блокировки и летучих.
Благодаря соответствующей комбинации правил HB может быть достигнуто правильное использование обычных общих переменных.
Хорошо, вышеупомянутое содержимое этой статьи. Я надеюсь, что содержание этой статьи имеет определенную справочную ценность для каждого обучения или работы. Если у вас есть какие -либо вопросы, вы можете оставить сообщение для общения. Спасибо за поддержку Wulin.com.