Когда несколько потоков обращаются к одному и тому же объекту, если планирование и альтернативная работа этих потоков в среде выполнения не учитываются, дополнительная синхронизация не требуется или любая другая операция координации выполняется в вызывающем объекте для вызова поведения этого объекта. может получить правильный результат, то объект потокобезопасен. Понимание определения заключается в том, что доброжелательный видит доброжелательный, а мудрый видит мудрость. Проблема безопасности потоков, как правило, вызвана несогласованностью и переупорядочением данных основной и рабочей памяти, и самое главное для решения проблемы безопасности потоков — это понять, откуда возникают эти две проблемы, а затем — суть их понимания. заключается в понимании модели памяти Java (JMM).
1. Модель памяти виртуальной машины JMM
Модель памяти Java или модель памяти Java, для краткостиJMM
, который представляет собой абстрактное понятие или протокол, используемый для решения проблемы доступа к памяти в процессе параллельного программирования, при этом будучи совместимым с различным оборудованием и операционными системами.Принцип JMM аналогичен принципу согласованности оборудования. В аппаратно-согласованной реализации каждый ЦП имеет кеш, и каждый ЦП взаимодействует со своим собственным кешем для чтения и записи данных в общую память и из нее.
Как показано на рисунке ниже, в модели памяти Java все переменные хранятся в основной памяти. Каждый поток Java имеет свою собственную рабочую память. В рабочей памяти хранится копия переменных, используемых потоком. Чтение и запись переменных потоком выполняются в рабочей памяти, а основной памятью нельзя манипулировать напрямую или он напрямую обращается к другой рабочей памяти потока. Когда значение переменной передается между потоками, оно должно проходить через основную память.
Когда два потока A и B взаимодействуют друг с другом, они проходят следующие два шага:
- Поток A считывает общую переменную из основной памяти в рабочую память потока A и обрабатывает ее, а затем перезаписывает данные обратно в основную память;
- Поток B считывает последнюю общую переменную из основной памяти.
Ключевое слово volatile позволяет принудительно сбрасывать переменную volatile в основную память каждый раз, делая ее видимой для каждого потока.
Следует отметить, что разделение областей памяти JMM и Java представляет собой различный концептуальный уровень, точнее, JMM описывает набор правил, которые контролируют методы доступа к различным переменным в программе в области общих данных и области личных данных. В JMM основная память принадлежит общей области данных, которая должна в определенной степени включать область кучи и методов, в то время как частная область данных потока данных рабочей памяти должна включать в себя счетчик программ, стек виртуальной машины и локальную память. область в определенной степени метод стек.
Взаимодействие между памятью
Принцип взаимодействия между основной памятью и рабочей памятью и связи между потоками в JMM представлен выше, но как передавать переменные между каждой памятью, JMM определяет 8 операций для реализации связи между основной памятью и рабочей памятью.Специфический протокол взаимодействия:
-
lock
(блокировка): переменная, действующая на основную память, идентифицирующая переменную как состояние монопольного потока; -
unlock
(Разблокировка): воздействует на переменную основной памяти, освобождает переменную в заблокированном состоянии, и освобожденная переменная может быть заблокирована другими потоками; -
read
(Чтение): воздействовать на переменную основной памяти и передавать значение переменной из основной памяти в рабочую память потока для использования последующим действием загрузки; -
load
(Загрузка): переменная, действующая на рабочую память, которая помещает значение переменной, полученное из основной памяти в результате операции чтения, в копию переменной рабочей памяти; -
use
(Использование): переменная, воздействующая на рабочую память, передавая значение переменной в рабочей памяти механизму выполнения, который будет выполняться всякий раз, когда виртуальная машина встречает инструкцию байт-кода, которая должна использовать значение переменной; -
assign
(присвоение): переменная, действующая на рабочую память, которая присваивает значение, полученное от исполнительного механизма, переменной в рабочей памяти и выполняет эту операцию всякий раз, когда виртуальная машина встречает инструкцию байт-кода, которая присваивает значение переменной; -
store
(Хранилище): переменная, действующая на рабочую память, передающая значение переменной из рабочей памяти в основную память для последующих операций записи; -
write
(Запись): переменная, действующая на основную память, которая переносит операцию сохранения из значения переменной в рабочей памяти в переменную в основной памяти.
Если вы хотите скопировать переменную из основной памяти в рабочую память, вам нужно сделать это последовательноread
а такжеload
операции, если переменные синхронизируются из рабочей памяти обратно в основную память, они выполняются последовательноstore
а такжеwrite
работать. Модель памяти Java требует только последовательного выполнения двух вышеуказанных операций, и нет никакой гарантии, что они должны выполняться последовательно. то естьread
а такжеload
между,store
а такжеwrite
Между ними могут быть вставлены другие инструкции, например, переменные в основной памяти.a
,b
При доступе возможный порядокread a
,read b
,load b
,load a
.
Модель памяти Java также предусматривает соблюдение следующих правил при выполнении вышеуказанных восьми основных операций:
- не положено
read
а такжеload
,store
а такжеwrite
одна из операций появляется одна; - Потоку не разрешается отбрасывать свои самые последние
assign
Операция, то есть переменная, должна быть синхронизирована с основной памятью после изменения переменной в рабочей памяти; - Тема не разрешена без причины (ничего не произошло
assign
операции) для синхронизации данных из оперативной памяти обратно в основную память; - Новую переменную можно создать только в основной памяти, прямое использование неинициализированной (загрузить или присвоить) переменной в рабочей памяти не допускается. то есть реализовать переменную
use
а такжеstore
Перед операцией необходимо выполнитьassign
а такжеload
действовать; - К переменной может обращаться только один поток за раз
lock
действовать,lock
а такжеunlock
должны появляться парами; - Если вы выполняете переменную
lock
операции значение этой переменной в рабочей памяти будет очищено, и ее необходимо выполнить повторно, прежде чем исполнительный механизм использует эту переменнуюload
илиassign
управлять значением переменной инициализации; - Если переменная ранее не была
lock
операция заблокирована, ее выполнение не разрешеноunlock
операцию, и не разрешается разблокировать переменную, заблокированную другими потоками; - выполнить по переменной
unlock
Перед операцией переменную необходимо синхронизировать с оперативной памятью (выполнитьstore
а такжеwrite
действовать).
Кроме того, виртуальная машина также устанавливает некоторые специальные правила для ключевого слова volialate, long и double.
Две роли ключевого слова volatile
- Обеспечьте видимость переменных: когда переменная, измененная ключевым словом volate, изменяется потоком, другие потоки могут немедленно получить измененный результат. Когда поток записывает данные в переменную, украшенную ключевым словом volate, виртуальная машина принудительно сбрасывает их в основную память со значением. Когда поток использует значение, украшенное ключевым словом volate, виртуальная машина заставляет его читать из основной памяти.
- Маскирование переупорядочивания инструкций: переупорядочивание инструкций — это метод, используемый компиляторами и процессорами для эффективной оптимизации программ.Он может только гарантировать правильность результатов выполнения программы, но не может гарантировать, что порядок выполнения программы соответствует порядку кода. Это не создает проблемы в однопоточном режиме, но создает проблему в многопоточном режиме. Очень классический пример — одновременное добавление переменных к полям в одноэлементном методе, просто для предотвращения переупорядочения инструкций. Чтобы проиллюстрировать это, рассмотрим следующий пример.
Давайте возьмем следующую программу в качестве примера, чтобы проиллюстрировать, как volate предотвращает переупорядочивание инструкций:
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) { // 1
sychronized(Singleton.class) {
if (singleton == null) {
singleton = new Singleton(); // 2
}
}
}
return singleton;
}
}
На самом деле, когда программа выполняется в 2 местах, если мы не используем ключевое слово voliate для изменения переменной singleton, это может вызвать ошибку. Это потому, что использованиеnew
Процесс инициализации объекта по ключевому слову не является атомарной операцией, он делится на следующие три шага:
- Выделить память для синглтона
- Вызов конструктора Singleton для инициализации переменных-членов
- Укажите объект-синглтон на выделенное пространство памяти (после этого шага синглтон будет ненулевым)
Если виртуальная машина имеет оптимизацию переупорядочения инструкций, порядок шагов 2 и 3 определить невозможно. Если поток A первым входит в блок синхронизированного кода и выполняет 3 без выполнения 2, это происходит потому, что синглтон уже не нулевой. В это время поток B достигает 1, решает, что синглтон не является нулевым, и возвращает его для использования, потому что синглтон на самом деле не был инициализирован в это время, и, естественно, произойдет ошибка.
Но обратите особое внимание на использование энергозависимых блокировок с двойной проверкой в версиях до jdk 1.5. Причина в том, что JMM (модель памяти Java) до Java 5 несовершенна. Даже объявление переменных как volatile не может полностью избежать переупорядочения, главным образом потому, что код до и после volatile-переменных все еще имеет проблемы с переупорядочением. Эта проблема переупорядочивания изменчивой маскировки была исправлена в jdk 1.5 (JSR-133). В это время jdk улучшил семантику volatile и добавил барьеры чтения и записи памяти для volatile объектов, чтобы обеспечить видимость. В настоящее время 2 -3 становится кодом порядок и не будет переставлен ЦП, так что после этого можно смело использовать volatile.
Специальные положения для длинных и двойных
В дополнение к специальным положениям для ключевого слова volatile, виртуальная машина также делает некоторые специальные положения для long и double: операции чтения и записи переменных типа long и double, которые не изменяются с помощью volatile, могут быть разделены на две части. -битные операции. То есть чтение и запись в long и double не являются атомарными и выполняются в два этапа. Однако вы можете гарантировать атомарность операций чтения и записи, объявив их недействительными.
происходит-до и как-будто сериал
Модель памяти Java определяется различными операциями.JMM определяет отношения частичного порядка для всех операций в программе, что является принципом «Происходит до». Это основная основа для оценки наличия конкуренции в данных и безопасности потока. Чтобы гарантировать, что поток, выполняющий операцию B, увидит результат операции A, между A и B должно быть выполнено отношение Happens-before, иначе JVM может упорядочить их произвольно.
Принцип первого вхождения в основном включает следующие пункты: Когда любое из следующих соотношений выполняется между двумя переменными, мы можем судить, что между ними существует последовательность, и они выполняются последовательно.
-
程序次序规则 (Program Order Rule)
: В том же потоке, в соответствии с последовательностью программного кода, операция, написанная впереди, происходит раньше, чем операция, написанная сзади. Если быть точным, то это последовательность выполнения программы с учетом ответвлений и циклов; -
管理锁定规则 (Monitor Lock Rule)
: операция разблокировки выполняется первой перед операцией блокировки того же замка (во временной последовательности); -
volatile 变量规则 (Volatile Variable Rule)
: операция записи в volatile переменную происходит за предшествующей (по временной последовательности) операции чтения переменной; -
线程启动规则 (Thread Start Rule)
: Тема объектаstart()
Этот метод выполняется первым для каждого действия в этом потоке; -
线程终止规则 (Thread Termination Rule)
: Все операции потока происходят сначала при обнаружении завершения этого потока, что можно проверить с помощьюThread.join()
метод заканчивается,Thread.isAlive()
Возвращаемое значение и другие средства определения того, что поток завершил выполнение; -
线程中断规则 (Thread Interruption Rule)
: нитьinterrupt()
Вызов метода происходит первым, когда код прерванного потока обнаруживает прерывание.Thread.interrupted()
Может определить, происходит ли прерывание; -
对象终结规则 (Finilizer Rule)
: Инициализация объекта (окончание выполнения конструктора) происходит до егоfinalize()
с начать; -
传递性 (Transitivity)
: Если операция А происходит раньше операции Б, а операция Б происходит раньше операции С, то можно сделать вывод, что А происходит раньше операции С.
Между хронологической последовательностью различных операций и принципом первого появления нет никакой связи. Эти два явления не могут быть выведены друг из друга. Измерение проблем безопасности параллелизма не может зависеть от временной последовательности. Все должно основываться на принципе первого появления. вхождение.
Если две операции обращаются к одной и той же переменной, и одна из двух операций является операцией записи, то эти две операции имеют зависимости по данным.Возможны три ситуации: 1) Запись после чтения; 2) После записи Запись; 3) Чтение. после записи все три операции зависят от данных, и изменение порядка повлияет на окончательный результат выполнения. Компилятор и процессор будут учитывать зависимости данных при переупорядочивании, а компилятор и процессор не изменят порядок выполнения двух операций, которые имеют зависимости данных.
И естьas-if-serialСемантика: независимо от переупорядочения (компиляторы и процессоры для обеспечения параллелизма) результат выполнения (однопоточной) программы не может быть изменен. Компилятор, среда выполнения и процессор должны подчиняться семантике как-если-последовательной. Семантика «как если бы» гарантирует, что результат выполнения однопоточной программы не изменится, а отношение «происходит до» гарантирует, что результат выполнения правильно синхронизированной многопоточной программы не изменится.
Принцип «происходит до» (случается до) и семантика «как если бы» — это принципы, которым следует виртуальная машина для оптимизации параллелизма провайдера, чтобы гарантировать, что результат выполнения останется неизменным.Первый подходит для мульти- многопоточных ситуаций, а последний подходит для однопоточной среды.
2. Java-поток
2.1 Реализация потоков Java
В системах Windows и Linux реализация потоков Java основана на модели потоков «один к одному» Так называемая модель «один к одному» на самом деле представляет собой модель потоков, которая косвенно вызывает ядро системы через программы уровня языка. то есть мы используем Java. При запуске потока виртуальная машина Java внутренне вызывает поток ядра текущей операционной системы для выполнения текущей задачи. Здесь вам нужно понять термин, поток ядра (Kernel-Level Thread, KLT), который представляет собой поток, поддерживаемый ядром операционной системы (Kernel).Этот тип потока выполняется ядром операционной системы для завершения переключения потоков. выполнять планирование и отображать задачи потока на отдельные процессоры. Каждый поток ядра можно рассматривать как клон ядра, поэтому операционная система может выполнять несколько задач одновременно. Так как многопоточная программа, которую мы пишем, относится к языковому уровню, то программа, как правило, напрямую не вызывает поток ядра. Вместо этого она представляет собой легковесный процесс (Light Weight Process), который также является потоком в обычном понимании. Процессы уровня ядра сопоставляются с потоком ядра, поэтому мы можем вызывать поток ядра через облегченный процесс, а затем ядро операционной системы сопоставляет задачу каждому процессору. называется моделью потоковой передачи один к одному.
Как показано на рисунке, каждый поток в конечном итоге будет сопоставлен ЦП для обработки.Если ЦП имеет несколько ядер, то один ЦП сможет выполнять несколько задач потока параллельно.
2.2 Безопасность потоков
Java может использовать три способа обеспечения потокобезопасности программы: 1) Взаимоисключающая синхронизация; 2) Неблокирующая синхронизация; 3) Отсутствие синхронизации.
Синхронизация взаимного исключения
Самый простой способ использовать синхронизацию в Java — это использоватьsychronized
ключевое слово, которое формируется до и после блоков синхронизированного кода после компиляцииmonitorenter
а такжеmonitorexit
инструкции байт-кода. Оба этих байт-кода требуют параметра ссылки на тип, чтобы указать объект, который нужно заблокировать и разблокировать. Если параметр объекта явно указан в программе Java, будет использоваться объект, в противном случае экземпляр объекта или объект класса будут использоваться в качестве объекта блокировки в зависимости от того, является ли синхронизированная модификация методом экземпляра или методом класса.
синхронизированный по своей сути имеетповторный вход: Согласно требованиям виртуальной машины, при выполнении синхронизированной инструкции сначала попытайтесь получить блокировку объекта. Если объект не заблокирован или текущий поток уже владеет блокировкой объекта, увеличьте счетчик блокировки на 1 и выполните соответствующее выполнение.monitorexit
Счетчик блокировки уменьшается на 1, когда инструкция выполняется, и блокировка снимается, когда счетчик достигает 0. Если получение блокировки объекта не удается, текущий поток заблокируется и будет ждать, пока блокировка объекта не будет снята другим потоком.
Помимо использования sychronized, мы также можем использовать ReentrantLock в JUC для достижения синхронизации, которая аналогична sychronized, разница в основном заключается в следующих трех аспектах:
- Ожидание может быть прервано: когда поток, удерживающий блокировку, не освобождает блокировку в течение длительного времени, ожидающий поток может отказаться от ожидания;
- Справедливая блокировка: когда несколько потоков ожидают одной и той же блокировки, они должны получить блокировку в том порядке, в котором блокировка была применена; нечестная блокировка не может быть гарантирована, и любой ожидающий поток может получить блокировку, когда блокировка снята. Блокировка Sychronized сама по себе является несправедливой блокировкой, а ReentrantLock является несправедливой по умолчанию, и может потребоваться, чтобы она была справедливой через конструктор.
- Блокировки могут быть привязаны к нескольким условиям: ReentrantLock может связывать несколько объектов Condition, а sychronized должен добавить блокировку для связи с несколькими условиями, а ReentrantLock нужно только вызвать newCondition несколько раз.
До JDK1.5 sychronized был хуже, чем ReentrantLock в многопоточной среде, но в JDK1.6 и выше виртуальная машина оптимизировала производительность sychronized, и производительность больше не является основным фактором для использования ReentrantLock вместо sychronized.
неблокирующая синхронизация
Так называемая неблокирующая синхронизация означает, что нет необходимости приостанавливать поток во время процесса синхронизации, что относится к синхронизации с взаимным исключением. Синхронизация с взаимным исключением — это, по сути, пессимистичная стратегия параллелизма, а неблокирующая синхронизация — оптимистичная стратегия параллелизма. Многие параллельные компоненты в JUC реализованы по принципу CAS, так называемый CAS — Compare-And-Swape, аналогичный оптимистической блокировке. Но в отличие от известной оптимистической блокировки, он включает в себя три значения: «новое значение», «старое значение» и «значение в памяти» при оценке, а при реализации используется бесконечный цикл, каждый раз, когда сравнивается «старое значение». со «значением в памяти», если два значения совпадают, это означает, что «значение в памяти» не было изменено другими потоками, в противном случае оно было изменено и его нужно прочитать снова. является «старым значением», а затем для оценки используются «старое значение» и «значение в памяти». Пока «старое значение» не будет таким же, как «значение в памяти», обновите «новое значение» в памяти.
Здесь следует отметить, что приведенная выше операция CAS разделена на 3 этапа, но эти 3 этапа должны быть выполнены одновременно, потому что в противном случае, когда «значение в памяти» оценивается как равное «старому значению», "новое значение" записывается в память. "Значения" изменяются другими потоками, и вы можете получить ошибочные результаты. в JDKsun.misc.Unsafe
серединаcompareAndSwapInt
Для завершения этой операции используется ряд методов Native. Также обратите внимание на некоторые проблемы с описанной выше операцией CAS:
- Типичная проблема ABA, то есть когда значение в памяти модифицируется потоком и изменяется обратно, текущий поток видит то же значение, что и ожидалось, но на самом деле было изменено другими потоками. Для решения проблемы ABA можно использовать традиционную стратегию взаимного исключения синхронизации.
- Еще одна проблема с CAS заключается в том, что он может вращаться слишком долго. Поскольку CAS не блокируется и синхронизируется, хотя поток не будет приостановлен, он будет вращаться (не что иное, как бесконечный цикл) для следующей попытки.Если время вращения слишком велико, это будет потреблять много производительности.
- Из приведенного выше описания также видно, что CAS может гарантировать атомарность только одной общей переменной и не может гарантировать ее при наличии нескольких переменных. Одно из решений состоит в том, чтобы упаковать несколько общих переменных в одну, то есть определить их как объект в целом и использовать CAS для обеспечения атомарности целого, например
AtomicReference
.
Нет схемы синхронизации
Так называемая схема без синхронизации означает, что синхронизация не требуется.
- Например, некоторые коллекции являются неизменяемыми коллекциями, тогда их не нужно синхронизировать.
- Есть несколько методов, его роль - функция, которая чаще встречается в идеях функционального программирования.Такая функция может предсказывать вывод через ввод, а переменные, участвующие в расчете, являются локальными переменными, поэтому нет необходимости синхронизировать .
- Существует также локальная переменная потока, напримерThreadLocalЖдать.
2.3 Оптимизация блокировки
Спин-блокировки и адаптивный спин
блокировка спинаОн используется для решения проблемы переключения потоков в процессе синхронизации взаимоисключения, поскольку само переключение потоков имеет определенные накладные расходы. Если физическая машина имеет более одного процессора и может позволить одновременное выполнение двух или более потоков, мы можем заставить поток, который запрашивает блокировку, «ждать некоторое время», но не отказываться от времени выполнения потока. процессор, посмотрите, скоро ли снимет блокировку поток, удерживающий блокировку. Чтобы заставить поток ждать, нам просто нужно заставить поток выполнить цикл занятости (spin), метод, известный как блокировка вращения.
Спин-блокировка появилась в JDK 1.4.2, но по умолчанию она отключена и ее можно использовать.-XX:+UseSpinning
параметр для открытия, в JDK 1.6 был изменен на открытый по умолчанию. Хотя ожидание вращения само по себе позволяет избежать накладных расходов на переключение потоков, оно занимает процессорное время, поэтому, если блокировка занята на короткое время, эффект ожидания вращения будет очень хорошим, в противном случае, если блокировка занята в течение длительного времени. слишком длинный, крутящийся поток будет только напрасно расходовать ресурсы процессора и не будет выполнять никакой полезной работы, но приведет к потере производительности.
Мы можем передать параметр-XX:PreBlockSpin
чтобы указать количество вращений, значение по умолчанию — 10 раз. Представлено в JDK 1.6Адаптивная спин-блокировка. Адаптивный означает, что время вращения больше не является фиксированным, а определяется предыдущим временем вращения того же замка и состоянием владельца замка. Если на том же объекте блокировки цикл ожидания только что успешно захватил блокировку, а поток, удерживающий блокировку, работает, виртуальная машина будет думать, что этот цикл, скорее всего, снова будет успешным, и разрешит цикл ожидания. относительно долгое время, например, 100 петель. С другой стороны, если вращение редко удается успешно получить для блокировки, можно пропустить процесс вращения при получении блокировки в будущем, чтобы не тратить ресурсы процессора впустую.
Ниже приведен пример реализации спин-блокировки:
public class SpinLock {
private AtomicReference<Thread> sign = new AtomicReference<>();
public void lock() {
Thread current = Thread.currentThread();
while(!sign.compareAndSet(null, current)) ;
}
public void unlock() {
Thread current = Thread.currentThread();
sign.compareAndSet(current, null);
}
}
Из приведенного выше примера мы можем видеть, что спин-блокировка блокируется и снимается, сравнивая, соответствует ли значение периода ожиданиям с помощью операции CAS. В методе блокировки, если значение в sign равно null, блокировка маркера снимается, в противном случае блокировка занята другими потоками и должна ждать в цикле. В методе разблокировки уведомите ожидающий поток о том, что блокировка снята, установив для знака значение null.
блокировка огрубления
Следует хорошо понимать концепцию укрупнения блокировки, которая заключается в объединении операций блокировки и разблокировки, которые соединяются несколько раз, в одну, и расширении нескольких последовательных блокировок в блокировку с большей областью действия.
public class StringBufferTest {
StringBuffer sb = new StringBuffer();
public void append(){
sb.append("a");
sb.append("b");
sb.append("c");
}
}
Здесь каждый звонокsb.append()
Все методы требуют блокировки и разблокировки.Если виртуальная машина обнаружит серию операций блокировки и разблокировки на одном и том же объекте, она будет объединена в более широкий диапазон операций блокировки и разблокировки, то есть в первуюappend()
метод заблокирован, последний разappend()
Разблокировать после окончания метода.
Легкий замок
Облегченные блокировки используются для решения проблемы потребления производительности тяжеловесных блокировок в процессе взаимного исключения.sychronized
Блокировка реализуется ключевым словом.synchronized
Это достигается с помощью внутреннего объекта, называемого блокировкой монитора. Однако характер блокировки монитора зависит от базовой операционной системы.Mutex Lock
быть реализованным. Операционная система должна переключаться из пользовательского режима в основной режим для переключения между потоками, что очень дорого, а переход между состояниями занимает относительно много времени.
Во-первых, в заголовке объекта есть раздел, который называетсяMark word
, в котором хранятся данные времени выполнения объекта, такие как хэш-код, возраст сборщика мусора и т. д., из которых 2 бита используются для хранения бита флага блокировки.
Когда код входит в синхронизированный блок, если состояние блокировки объекта является свободным от блокировки (флаг блокировки равен «01»), виртуальная машина сначала создаст锁记录
(Lock Record
) пространство для хранения текущего объекта блокировкиMark Word
копировать. После успешного копирования виртуальная машина будет использовать операцию CAS, чтобы попытаться скопировать содержимое объекта.Mark Word
обновлено, чтобы указать наLock Record
указатель иLock Record
внутреннийowner
указатель вправоMark word
. и объектMark Word
флаг блокировки становится равным "00", указывая на то, что объект заблокирован. Если операция обновления не удалась, виртуальная машина сначала проверяетMark Word
Указывает ли он на кадр стека текущего потока, если да, то это означает, что текущий поток уже владеет блокировкой этого объекта, тогда он может напрямую войти в блок синхронизации для продолжения выполнения. В противном случае это означает, что несколько потоков конкурируют за блокировки, а легкие блокировки расширятся до тяжелых, а флаг блокировки станет равным «10».Mark Word
В нем хранится указатель на тяжеловесную блокировку (мьютекс), и поток, ожидающий блокировки, также перейдет в состояние блокировки. Текущий поток пытается использовать вращение для получения блокировки.Спин — это процесс использования цикла для получения блокировки, чтобы не блокировать поток.
Из приведенного выше видно, что на самом деле, когда поток получает облегченную блокировку объекта, объектMark Word
будет указывать на кадр стека потокаLock Record
, а кадр стекаLock Record
также укажет на объектMark Word
. в кадре стекаLock Record
Используется для определения блокировки объекта текущим потоком и блокировки объекта.Mark Word
Используется для определения того, какой поток удерживает блокировку текущего объекта. Когда поток пытается получить блокировку объекта, он сначала определяет, заблокирован ли текущий объект с помощью флага блокировки, а затем определяет, является ли поток, в настоящее время получающий блокировку объекта, текущим потоком посредством операции CAS.
Облегченные блокировки не предназначены для замены тяжелых замков, потому что они добавляют дополнительные операции CAS в дополнение к блокировке, поэтому легкие блокировки могут быть медленнее, чем традиционные тяжелые блокировки в конкурентных ситуациях.
Блокировка смещения
Когда объект создается впервые, потоки для доступа к нему отсутствуют. Это означает, что теперь он считает, что только один поток может получить к нему доступ, поэтому, когда первый поток обращается к нему, он отдает предпочтение этому потоку. В этот момент объект удерживает смещенную блокировку, смещенную в сторону первого потока. Этот поток использует операцию CAS при изменении заголовка объекта, чтобы он стал предвзятой блокировкой, и изменяет ThreadID в заголовке объекта на свой собственный идентификатор.При повторном доступе к объекту ему нужно только сравнить идентификатор и не нужно использовать CAS для работы.
После того, как вторая нить обращается к объекту, поскольку предвзятый замок не будет активно выпущен, второй поток может видеть предвзятое состояние объекта, что указывает, что уже есть конкуренция на этом объекте, и проверяет, что замок объекта был изначально проводится. Если нить по-прежнему жива, если она зависает, вы можете изменить объект в состояние без блокировки, а затем переосмыслить новую нить. Если оригинальная нить все еще жива, операционная стопка этой резьбы выполняется Немедленно проверять использование объекта. Если необходимо удерживать смещение блокировки, предвзятый замок обновляется до легкого блокировки (предвзятый замок обновляется до легкого блокировки в это время). Если он больше не используется, объект может быть возвращен в состояние свободного блокировки, а затем перенаправлено.
Облегченные блокировки считают, что конкуренция существует, но степень конкуренции очень легкая.Как правило, два потока будут смещать работу одного и того же замка или немного подождать (прокрутить), а другой поток освободит блокировку. Но когда вращение превышает определенное количество раз, или один поток удерживает блокировку, один крутится и происходит третье посещение, облегченная блокировка расширяется в тяжеловесную блокировку, а тяжеловесная блокировка делает все потоки, кроме потока, который владеет блокировкой.Все потоки блокируются, предотвращая простаивание процессора.
Если большую часть времени к блокировке всегда обращаются несколько разных потоков, то смещенный режим является избыточным и может быть выполнен с помощью-XX:-UserBiaseLocking
Оптимизация предвзятой блокировки отключена.
Предложение облегченных блокировок и предвзятых блокировок основано на том факте, что в большинстве случаев поток, который получает объектную блокировку, является одним и тем же потоком, и его эффективность в этом случае будет выше, чем у тяжеловесных блокировок. не обязательно более эффективен, чем тяжелые замки. Поэтому они не предлагаются для замены тяжеловесных блокировок, но в некоторых сценариях они более эффективны, чем тяжеловесные блокировки, поэтому мы можем указать, включать ли их через параметры виртуальной машины в соответствии с нашими собственными сценариями приложений.
Суммировать
JMM является теоретической основой для реализации параллелизма в Java.JMM определяет 8 операций и 8 правил, а также делает специальные условия для типов volate, long и double.
JVM переупорядочит наш код для оптимизации производительности.Для переупорядочивания JMM предлагает принцип «происходит до» (случается-до) и семантику «как если-последовательно», чтобы гарантировать, что конечный результат программы не изменится из-за переупорядочивания.
Потоки Java реализуются посредством упрощенного отображения на потоки ядра. Мы можем использовать взаимоисключающую синхронизацию, неблокирующую синхронизацию и отсутствие синхронизации для обеспечения безопасности потоков в многопоточных ситуациях. Кроме того, Java также предоставляет множество стратегий оптимизации блокировок для повышения производительности кода в многопоточных ситуациях.
Содержимое JMM в основном представлено здесь, поэтому представленное содержимое, связанное с параллелизмом, представляет только часть, связанную с JMM. Но чтобы по-настоящему изучить содержимое параллелизма и параллельных пакетов, нам нужно прочитать еще много исходных кодов, и объем одной статьи явно не может охватить их все.