Введение
До jdk1.6 мы бы сказали, что synchronized — это тяжеловесная блокировка.После этого JVM провела большую оптимизацию над ней, а затем, используя синхронизированные потоки для получения блокировок, можно было получить предвзятые блокировки, облегченные блокировки и блокировки веса в соответствии с соревнование состояние блокировка уровня.
В технологии блокировки есть некоторые из них, такие как огрубление блокировки, устранение блокировки, спин-блокировка и адаптивная спин-блокировка. Что это такое? В этой статье мы объясним их по порядку.
Обратите внимание, что речь идет о синхронизированной синхронизации, то есть о неявной блокировке. Это еще один способ реализовать блокировку с помощью Lock.
Что такое тяжеловесный замок
Чтобы понять, почему JVM оптимизирует его, мы должны сначала понять, что такое тяжеловесная блокировка и почему ее следует оптимизировать.Давайте посмотрим на фрагмент кода.
public synchronized void f() {
System.out.println("hello world");
}
После декомпиляции javap
public synchronized void f();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String hello world
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 3: 0
line 4: 8
Когда поток обращается к этому методу, он сначала проверяет, существует ли ACC_SYNCHRONIZED.Если он есть, ему необходимо получить соответствующую блокировку монитора, прежде чем он сможет быть выполнен.
Блокировка монитора снимается, когда метод завершается или в середине возникает необработанное исключение.
В Hotspot эти операции реализованы через ObjectMonitor, с помощью предоставляемых им функций можно получать блокировки, снимать блокировки, ждать освобождения блокировки во время блокировки и затем конкурировать за блокировки, и ждать пробуждения блокировки .Давайте обсудим что это такое.Как это сделать.
Каждый объект содержит монитор.Монитор — это механизм синхронизации, с помощью которого мы можем добиться взаимоисключающего доступа между потоками.Сначала давайте перечислим несколько ключевых полей ObjectMonitor, которые нам нужно обсудить.
- _owner, какой поток является ObjectMonitor, которым в данный момент владеет
- _entryList, блокирующая очередь (блокирует некоторые потоки, конкурирующие за получение блокировок)
- _WaitSet, поток в очереди ожидания должен дождаться пробуждения (может быть возвращен прерыванием, сигналом, тайм-аутом и т. д.)
- _cxq, потоку не удалось получить блокировку и поместить ее в очередь _cxq.
- _recursions, количество повторных входов потока, synchronized — это повторный вход в блокировку
Процесс выполнения блокировки потоков очереди, конкурирующих за блокировки, от начала потока, конкурирующего за блокировки, до конца метода и снятия блокировки показан на рисунке выше.Затем давайте проанализируем две ситуации получения блокировки и снятия блокировки.
при получении замка
при снятии блокировки
До jdk1.6 синхронизация напрямую вызывала метод ввода ObjectMonitor для получения блокировки (первое изображение), а затем возвращалась к вызову метода выхода ObjectMonitor (второе изображение) при освобождении блокировки, которая называется тяжеловесной блокировкой. , вы можете увидеть, с какой операционной сложностью он связан.
Так что подумайте об этом
Если к нему одновременно обращается только один поток, то даже если у него есть общие переменные, поскольку к нему не будут обращаться несколько потоков одновременно, проблемы безопасности потоков нет. для выполнения тяжеловесного процесса блокировки. Просто используйте потокобезопасные операции в случае конкуренции
что приводит кБлокировка смещенияиЛегкий замок
блокировка спина
Спин-блокировки включены по умолчанию, начиная с jdk1.6. Поскольку пары пробуждения и приостановки тяжеловесных блокировок должны быть переданы из пользовательского режима в вызовы режима ядра для завершения, большое количество параллелизма будет оказывать большее давление на систему, поэтому спин-блокировки, по-видимому, позволяют избежать частых зависаний. операции.
Спин-блокировка означает, что поток A получил блокировку и выполняется, тогда поток B не блокируется при получении блокировки, не отказывается от времени выполнения ЦП и напрямую выполняет бесконечный цикл (ограниченное количество раз), чтобы непрерывно конкурировать для блокировки.Если скорость выполнения A достигается очень быстро, то поток B может получить объект блокировки для выполнения быстрее, тем самым избегая накладных расходов на приостановку и возобновление потоков, а также может дополнительно улучшить время отклика.
Количество спин-блокировок по умолчанию равно 10, что можно изменить с помощью -XX:PreBlockSpin.
адаптивный спин
Подобно блокировке вращения, разница в том, что время и время вращения больше не фиксированы. Например, для того же объекта блокировки, если последний спин успешно получил блокировку, то JVM будет думать, что в следующий раз блокировку можно будет получить успешно, тем самым позволяя спину получить блокировку на более длительное время. Если несколько вращений успешно получают блокировку одного и того же объекта блокировки, JVM может просто пропустить процесс вращения.
Спин-блокировки аналогичны адаптивным блокировкам.Хотя ожидание вращения позволяет избежать накладных расходов на переключение потоков, они не уменьшают время выполнения ЦП.Если блокировка занята в течение длительного времени, может быть много циклов и потерь.Ресурсы ЦП, поэтому спин-блокировку нельзя использовать для замены блокировки, у нее есть свои применимые сценарии.
Блокировка смещения
Блокировка будет смещена в сторону первого потока, который ее выполнит.Если к блокировке не обращаются другие потоки впоследствии, нам не нужно блокировать ее и выполнять ее напрямую.
Если впоследствии будет обнаружено, что другие потоки захватывают блокировку, то будет решено либо повторно сместить блокировку на новый поток, либо отменить смещенную блокировку и обновить ее до облегченной блокировки в соответствии со статусом потока, который получил замок перед.
Блокировка Mark Word идентифицируется следующим образом.
thread ID | - | Это нестандартный замок? | замок флаг |
---|---|---|---|
thread ID | epoch | 1 | 01 (разблокировано) |
Поток A — идентификатор потока равен 100. При получении блокировки обнаруживается, что флаг блокировки равен 01, а флаг блокировки смещения равен 1 (может быть смещен), а затем CAS записывает идентификатор потока в слово метки объекта. заголовок После успеха
thread ID | - | Это нестандартный замок? | замок флаг |
---|---|---|---|
100 | epoch | 1 | 01 (разблокировано) |
Когда A снова выполнит метод в будущем, вам нужно просто определить, является ли идентификатор потока в слове Mark заголовка объекта текущим потоком, и если это так, запустить его напрямую.
Если в это время есть другой поток потока B, пытающийся получить блокировку, поток B - идентификатор потока равен 101, также проверьте бит флага блокировки и может ли состояние быть смещенным, а затем CAS указывает идентификатор потока Mark Word на себя. , Обнаружение не удалось, так как ID потока уже указывал на поток A, то в это время будет выполняться операция отзыва смещенной блокировки, а поток с смещенной блокировкой (thread) будет приостановлен в глобальной безопасной точке ( байт-код не выполняется) A), а затем проверьте состояние потока A, тогда поток A имеет 2 ситуации в это время.
В первом случае поток A завершился, затем, после установки идентификатора потока Mark Word пустым, CAS сместит идентификатор потока на поток B, а затем снова вернется в вышеупомянутое рабочее состояние потока смещенной блокировки.
thread ID | - | Это нестандартный замок? | замок флаг |
---|---|---|---|
101 | epoch | 1 | 01 (разблокировано) |
Во втором случае, если поток A активен, смещенная блокировка будет обновлена до облегченной блокировки, а затем поток A будет разбужен для выполнения последующих операций, а поток B будет вращаться, чтобы получить облегченную блокировку.
thread ID | Это нестандартный замок? | замок флаг |
---|---|---|
нулевой | 0 | 00 (облегченный замок) |
Можно обнаружить, что блокировка смещения подходит для ситуации, когда только один поток выполняется от начала до конца, исключая накладные расходы на блокировку получения вращения и взаимное исключение тяжеловесной блокировки.Этот тип блокировки имеет наименьшие накладные расходы и лучшую производительность. в безблокировочное состояние, но если есть конкуренция между потоками, необходимо часто приостанавливать поток со смещенной блокировкой, а затем проверять состояние, чтобы решить, следует ли повторно сместить или перейти на облегченную блокировку, производительность будет значительно снижается, если вы можете заранее знать, что будет конкуренция, поэтому вы можете отключить блокировку смещения
Некоторые друзья скажут, что если есть конкуренция, то следует немедленно перейти на более тяжелый замок.Не обязательно, легкий замок будет понят ниже.
Легкий замок
Если нет конкуренции между потоками или случайной конкуренции, а выполнение кода в блокировке очень быстрое, то это очень подходит для облегченных сценариев блокировки.Если блокировка смещения должна полностью отменить синхронизацию, а также отменить CAS и процесс получения блокировки путем вращения, ему нужно только определить, указывает ли идентификатор потока в слове метки на себя (небольшое суждение в другие моменты времени можно игнорировать),Затем упрощенная блокировка заключается в использовании CAS и спин-блокировки для получения блокировки, чтобы снизить потребление производительности при использовании мьютекса операционной системы для завершения тяжелой блокировки.
Реализация легкого замка выглядит следующим образом
JVM создаст пространство для хранения записи блокировки в кадре стека текущего потока, а затем скопирует Mark Word заголовка объекта в запись блокировки, официально называемую Displaced Mark Word, Затем поток попытается использовать CAS для замены Пометить слово заголовка объекта указателем на блокировку записи
Предполагая, что поток B успешно заменен, он указывает, что блокировка успешно получена, а затем продолжает выполнение кода.В это время слово метки выглядит следующим образом.
указатель на стек потоков | статус блокировки |
---|---|
указатель стека 1 -> указывает на поток B | 00 (облегченный замок) |
В это время блокировку получает поток C. Когда CAS не удается изменить заголовок объекта, он не обнаруживает, что он занят потоком B, и затем ищет блокировку. В результате поток B просто завершает выполнение в это время и нить C спина успешно приобретает.
указатель на стек потоков | статус блокировки |
---|---|
указатель стека 2 -> поток C | 00 (облегченный замок) |
В это время поток D снова захватывает блокировку и обнаруживает, что она занята потоком C. Затем он выполняет поиск блокировки. После 10 циклов по умолчанию он обнаруживает, что соответствующую блокировку невозможно получить (поток C не освободил ее). еще), то поток D пометит слово как измененное на тяжеловесную блокировку.
указатель на стек потоков | статус блокировки |
---|---|
указатель стека 2 -> поток C | 10 (тяжелый замок) |
Затем, когда выполнение потока C завершается, когда слово метки в кадре стека заменяется обратно на слово метки заголовка объекта, обнаруживается, что есть другие потоки, конкурирующие за блокировку (состояние блокировки было изменено потоком). D), затем он освобождает блокировку и пробуждается в ожидании потока, все последующие операции с потоком являются тяжеловесными блокировками.
указатель на стек потоков | статус блокировки |
---|---|
нулевой | 10 (тяжелый замок) |
Следует отметить, что после обновления замка он не будет понижен.
снятие блокировки
Устранение блокировок в основном является операцией оптимизации JIT-компилятора. Сначала JIT-компилятор горячего кода скомпилирует его в машинный код. При выполнении последующих исполнений нет необходимости интерпретировать каждый байт-код класса как машинный код, а затем выполнять его. эффективности, он будет оптимизировать код в определенной степени на основе анализа побегов, такого как устранение блокировок, выделение стека и т. д.
public void f() {
Object obj = new Object();
synchronized(obj) {
System.out.println(obj);
}
}
Компилятор JIT обнаруживает, что к объекту в f() может получить доступ только один поток, поэтому он десинхронизируется.
public void f() {
Object obj = new Object();
System.out.println(obj);
}
замок огрубление
Если один и тот же объект многократно блокируется и разблокируется в фрагменте кода, это на самом деле является относительно ресурсоемким.В этом случае область блокировки может быть соответствующим образом ослаблена, чтобы снизить потребление производительности.
Когда JIT обнаруживает, что серия последовательных операций многократно блокирует и разблокирует один и тот же объект, даже если операция блокировки происходит в теле цикла, область синхронизации блокировки будет распространяться за пределы всей последовательности операций.
for (int i = 0; i < 10000; i++) {
synchronized(this) {
do();
}
}
Грубый код
synchronized(this) {
for (int i = 0; i < 10000; i++) {
do();
}
}
Ссылаться на:
- Искусство параллельного программирования на Java
- Глубокое понимание виртуальной машины Java
- Глубокое понимание параллельного программирования на Java