1. Введение
Ключевое слово synchronized — это самый простой метод синхронизации в Java. После компиляции он генерирует инструкции байт-кода monitorenter и monitorexit до и после блока синхронизации. Обе эти инструкции байт-кода требуют параметра ссылочного типа для указания желаемых заблокированных и разблокированных объектов. .
Во-вторых, проблема
- Синхронизированные функции?
- Принцип реализации синхронизирован?
- Является ли синхронизированный реентерабельным?
- Является ли синхронизированным честная блокировка?
- Синхронная оптимизация?
- Пять способов использования синхронизированы?
3. Принцип реализации
В модели памяти Java есть две инструкции:lock
иunlock
.
-
lock
, блокировка, действующая на переменную в основной памяти, идентифицирует переменную в основной памяти как исключительное состояние потока. -
unlock
, Unlock, воздействуя на переменные в основной памяти, освобождает заблокированные переменные, а освобожденные переменные могут быть заблокированы другими потоками.
Однако эти две инструкции не предоставляются пользователям напрямую, а представляют собой две инструкции более высокого уровня.monitorenter
иmonitorexit
использовать неявноlock
иunlock
инструкция.
иsynchronized
это использоватьmonitorenter
иmonitorexit
Эти две инструкции реализованы.
Согласно требованиям спецификации JVM, при выполненииmonitorenter
При выполнении инструкции сначала попытаться получить блокировку объекта.Если объект не заблокирован, или текущий поток уже владеет блокировкой объекта, счетчик блокировки увеличивается на 1, и соответственно при выполненииmonitorexit
Когда счетчик уменьшается на 1, когда счетчик уменьшается до 0, блокировка снимается.
Давайте перейдем к последнему фрагменту кода и посмотрим, как выглядит скомпилированный байт-код для изучения:
public class SynchronizedTest {
public static void sync() {
synchronized (SynchronizedTest.class) {
synchronized (SynchronizedTest.class) {
}
}
}
public static void main(String[] args) {
}
}
Наш код очень прост, просто дважды добавьте объект SynchronizedTest.class.synchronized
, кроме этого, ничего не сделал.
Инструкции байт-кода скомпилированного метода sync() выглядят следующим образом и специально прокомментированы для удобства чтения:
// 加载常量池中的SynchronizedTest类对象到操作数栈中
0 ldc #2 <com coolcoding code synchronize synchronizedtest>
// 复制栈顶元素
2 dup
// 存储一个引用到本地变量0中,后面的0表示第几个变量
3 astore_0
// 调用monitorenter,它的参数变量0,也就是上面的SynchronizedTest类对象
4 monitorenter
// 再次加载常量池中的SynchronizedTest类对象到操作数栈中
5 ldc #2 <com coolcoding code synchronize synchronizedtest>
// 复制栈顶元素
7 dup
// 存储一个引用到本地变量1中
8 astore_1
// 再次调用monitorenter,它的参数是变量1,也还是SynchronizedTest类对象
9 monitorenter
// 从本地变量表中加载第1个变量
10 aload_1
// 调用monitorexit解锁,它的参数是上面加载的变量1
11 monitorexit
// 跳到第20行
12 goto 20 (+8)
15 astore_2
16 aload_1
17 monitorexit
18 aload_2
19 athrow
// 从本地变量表中加载第0个变量
20 aload_0
// 调用monitorexit解锁,它的参数是上面加载的变量0
21 monitorexit
// 跳到第30行
22 goto 30 (+8)
25 astore_3
26 aload_0
27 monitorexit
28 aload_3
29 athrow
// 方法返回,结束
30 return
Согласно этому комментарию, байт-код относительно прост. Наш синхронизированный блокирует объект класса SynchronizedTest. Вы можете видеть, что он дважды загружает объект класса SynchronizedTest из пула констант и сохраняет их в локальной переменной 0 и локальной переменной 1 соответственно. это разблокировано, это в обратном порядке.Сначала разблокируйте переменную 1, а затем разблокируйте переменную 0. На самом деле переменная 0 и переменная 1 указывают на один и тот же объект, поэтому синхронизация является реентерабельной.
Что касается того, как заблокированный объект хранится в заголовке объекта, я не буду здесь вдаваться в подробности, если вам интересно, вы можете прочитать книгу «Искусство параллельного программирования на Java».
4. Атомарность | Видимость | Порядок
Когда мы ранее объясняли модель памяти Java, мы сказали, что модель памяти в основном используется для решения проблемы согласованности кэша, а согласованность кэша в основном включает в себя атомарность, видимость и упорядоченность.
Так,synchronized
Гарантирует ли ключевое слово эти три свойства?
Вернемся к модели памяти Java.synchronized
Базовое ключевое слово передается черезmonitorenter
иmonitorexit
реализуется, и эти две инструкции реализуютсяlock
иunlock
быть реализованным.
Блокировка и разблокировка в модели памяти Java должны соответствовать следующим четырем правилам:
(1) Переменная может быть заблокирована только одним потоком одновременно, но операция блокировки может выполняться одним и тем же потоком несколько раз.После многократного выполнения блокировки переменная может быть разблокирована только путем выполнения одного и того же числа. операций разблокировки.
(2) Если операция блокировки выполняется над переменной, значение переменной в рабочей памяти будет очищено.Прежде чем исполнительный механизм использует переменную, необходимо повторно выполнить операцию загрузки или назначения, чтобы инициализировать значение переменная;
(3) Если переменная не заблокирована операцией блокировки, операция разблокировки не может выполняться над ней, а также не разрешается разблокировать переменную, заблокированную другими потоками;
(4) Перед выполнением операции разблокировки переменной переменная должна быть синхронизирована обратно в основную память, то есть выполняются операции сохранения и записи;
Благодаря правилу (1) мы знаем, что для кода между блокировкой и разблокировкой одновременно разрешен доступ только одному потоку, поэтому синхронизация является атомарной.
По правилам (1) (2) и (4) мы знаем, что каждый разlock
иunlock
Когда переменная загружается из основной памяти или сбрасывается обратно в основную память,lock
иunlock
Переменная между (здесь относится к заблокированной переменной) не будет изменяться другими потоками, поэтомуsynchronized
видно.
Благодаря правилам (1) и (3) мы знаем, что все блокировки переменных должны быть поставлены в очередь, а другим потокам не разрешено разблокировать объекты, заблокированные текущим потоком, поэтомуsynchronized
упорядочен.
В итоге,synchronized
Атомарность, видимость и упорядоченность гарантируются.
5. Справедливая блокировка против несправедливой блокировки
Благодаря приведенному выше обучению мы знаем принцип реализации синхронизированного, и он является реентерабельным, тогда является ли это справедливой блокировкой? Следующий код:
public class SynchronizedTest {
public static void sync(String tips) {
synchronized (SynchronizedTest.class) {
System.out.println(tips);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(()->sync("线程1")).start();
Thread.sleep(100);
new Thread(()->sync("线程2")).start();
Thread.sleep(100);
new Thread(()->sync("线程3")).start();
Thread.sleep(100);
new Thread(()->sync("线程4")).start();
}
}
В этой программе мы запускаем четыре потока и запускаем их с интервалом в 100 мс.Каждый поток печатает предложение и ждет 1000 мс.Если синхронизация является справедливой блокировкой, напечатанные результаты должны быть потоком 1, 2, 3, 4.
Однако результаты фактической операции вряд ли будут выглядеть так, как указано выше, поэтому синхронизация является несправедливой блокировкой.
Шесть, оптимизация блокировки
Java постоянно развивается, и аналогично развиваются такие древние вещи, как синхронизация в Java.Например, ConcurrentHashMap был еще заблокирован ReentrantLock в jdk7, а в jdk8 заменен нативной синхронизацией.Видно, что синхронизировано с нативной поддержкой , ему еще есть куда развиваться.
Итак, каковы эволюционирующие состояния синхронизированного?
Давайте кратко представим это здесь:
(1) Предвзятая блокировка означает, что часть кода синхронизации всегда доступна потоку, после чего этот поток автоматически получает блокировку, снижая стоимость получения блокировки.
(2) Облегченная блокировка означает, что когда блокировка является смещенной блокировкой и к ней обращается другой поток, смещенная блокировка будет обновлена до облегченной блокировки. Этот поток попытается получить блокировку, вращаясь без блокировки. Повышение производительности.
(3) Тяжеловесная блокировка означает, что если замок является облегченным замком, то после того, как вращающаяся нить сделает определенное количество оборотов, она войдет в состояние блокировки, и замок будет обновлен до тяжеловесного замка без получения блокировки. заблокирует другие потоки и снизит производительность.
Семь, пять способов использования синхронизированных
Благодаря приведенному выше анализу мы знаем, что для synchronized требуется параметр ссылочного типа, и параметры этого ссылочного типа фактически можно разделить на три категории в Java: объекты класса, объекты экземпляра и обычные ссылки, которые используются следующим образом:
public class SynchronizedTest2 {
public static final Object lock = new Object();
// 锁的是SynchronizedTest.class对象
public static synchronized void sync1() {
}
public static void sync2() {
// 锁的是SynchronizedTest.class对象
synchronized (SynchronizedTest.class) {
}
}
// 锁的是当前实例this
public synchronized void sync3() {
}
public void sync4() {
// 锁的是当前实例this
synchronized (this) {
}
}
public void sync5() {
// 锁的是指定对象lock
synchronized (lock) {
}
}
}
При использовании synchronized в методе следует учитывать, что неявно передаются параметры, которые делятся на статические методы и нестатические методы.Неявный параметр в статическом методе — это текущий объект класса, а неявный параметр в не- статический метод — это текущий экземпляр this.
Кроме того, множественные синхронизированные блокируют только один и тот же объект, а код между ними синхронизируется, на это необходимо обращать внимание при использовании синхронизированных.
8. Резюме
- Synchronized генерирует инструкции байт-кода monitorenter и monitorexit до и после синхронизированного блока во время компиляции;
- Инструкции байт-кода monitorenter и monitorexit требуют параметр ссылочного типа, а не базового типа;
- Инструкции байт-кода monitorenter и monitorexit нижнего уровня представляют собой инструкции блокировки и разблокировки, использующие модель памяти Java;
- синхронизированный — это блокировка с повторным входом;
- синхронизированный — это нечестная блокировка;
- Synchronized может гарантировать атомарность, видимость и упорядоченность одновременно;
- Synchronized имеет три состояния: смещенная блокировка, облегченная блокировка и усиленная блокировка;
Замок |
преимущество |
недостаток |
Применимая сцена |
Блокировка смещения |
Блокировка и разблокировка не требуют дополнительного потребления, а разрыв составляет всего наносекунду по сравнению с выполнением асинхронных методов. |
Если между потоками существует конкуренция за блокировку, это приведет к дополнительному потреблению отзыва блокировки. |
Применимо только к одному потоку, обращающемуся к сценарию синхронизированного блока (только один поток входит в критическую секцию) |
Легкий замок |
Конкурирующие потоки не будут блокироваться, что повышает скорость отклика программы. |
Использование spin будет потреблять ресурсы ЦП, если нет доступных конкурирующих потоков. |
Погоня за скоростью отклика, скорость выполнения синхронизированного блока очень высокая (несколько потоков поочередно входят в критическую секцию) |
тяжелый замок |
Конкуренция потоков не использует вращение и не потребляет ЦП |
Блокировка потока и медленное время отклика |
В погоне за пропускной способностью синхронизированное выполнение блоков выполняется медленнее (несколько потоков входят в критическую секцию одновременно). |