Синхронный анализ мертвой серии синхронизации Java

Java

проблема

(1) Каковы характеристики синхронизации?

(2) Принцип реализации синхронизирован?

(3) Является ли синхронизированный реентерабельным?

(4) Синхронизируется ли справедливая блокировка?

(5) Оптимизация синхронизирована?

(6) Пять способов использования синхронизированных?

Введение

Ключевое слово synchronized — это самый простой метод синхронизации в Java. После компиляции он генерирует инструкции байт-кода monitorenter и monitorexit до и после блока синхронизации. Обе эти инструкции байт-кода требуют параметра ссылочного типа для указания желаемых заблокированных и разблокированных объектов. .

Принцип реализации

При изучении модели памяти Java мы ввели две инструкции: блокировку и разблокировку.

блокировка, блокировка, переменная, действующая на основную память, она идентифицирует переменную в основной памяти как состояние монопольного потока.

Разблокировать, разблокировать, воздействовать на переменные основной памяти, она освобождает заблокированные переменные, а освобожденные переменные могут быть заблокированы другими потоками.

Но эти две инструкции не предоставляются пользователю напрямую, а две инструкции более высокого уровня, monitorenter и monitorexit, предназначены для неявного использования инструкций блокировки и разблокировки.

А синхронизация реализуется с помощью двух инструкций monitorenter и monitorexit.

Согласно требованиям спецификации JVM, при выполнении инструкции monitorenter сначала попытаться получить блокировку объекта, если объект не заблокирован или текущий поток уже владеет блокировкой объекта, счетчик блокировки увеличивается на 1, и, соответственно, счетчик будет уменьшаться на 1 при выполнении monitorexit, а блокировка будет снята, когда счетчик уменьшается до 0.

Давайте перейдем к последнему фрагменту кода и посмотрим, как выглядит скомпилированный байт-код для изучения:

public class SynchronizedTest {

    public static void sync() {
        synchronized (SynchronizedTest.class) {
            synchronized (SynchronizedTest.class) {
            }
        }
    }

    public static void main(String[] args) {

    }
}

Наш код очень прост, он просто дважды добавляет синхронизированный объект SynchronizedTest.class и больше ничего не делает.

Инструкции байт-кода скомпилированного метода 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».

Ответьте на «JMM» в фоновом режиме официального аккаунта, чтобы получить pdf-версию этой книги.

Атомарность, Видимость, Порядок

Модель памяти Java, чтобы объяснить время перед нами, сказала, что модель памяти в основном используется для решения проблем когерентности кеша, а когерентность кеша включает атомарность, видимость, упорядоченность.

Итак, может ли ключевое слово synchronized гарантировать эти три характеристики?

Возвращаясь к модели памяти Java, нижний слой синхронизированного ключевого слова реализуется через monitorenter и monitorexit, а эти две инструкции реализуются через блокировку и разблокировку.

Блокировка и разблокировка в модели памяти Java должны соответствовать следующим четырем правилам:

(1) Переменная может быть заблокирована только одним потоком одновременно, но операция блокировки может выполняться одним и тем же потоком несколько раз.После многократного выполнения блокировки переменная может быть разблокирована только путем выполнения одного и того же числа. операций разблокировки.

(2) Если операция блокировки выполняется над переменной, значение переменной в рабочей памяти будет очищено.Прежде чем исполнительный механизм использует переменную, необходимо повторно выполнить операцию загрузки или назначения, чтобы инициализировать значение переменная;

(3) Если переменная не заблокирована операцией блокировки, операция разблокировки не может выполняться над ней, а также не разрешается разблокировать переменную, заблокированную другими потоками;

(4) Перед выполнением операции разблокировки переменной переменная должна быть синхронизирована обратно в основную память, то есть выполняются операции сохранения и записи;

Благодаря правилу (1) мы знаем, что для кода между блокировкой и разблокировкой одновременно разрешен доступ только одному потоку, поэтому синхронизация является атомарной.

Благодаря правилам (1), (2) и (4) мы знаем, что каждый раз, когда мы блокируем и разблокируем, переменные будут загружаться из основной памяти или сбрасываться обратно в основную память, а переменные между блокировкой и разблокировкой ( здесь относится к заблокированной переменной ) не изменяется другими потоками, поэтому синхронизация видна.

Благодаря правилам (1) и (3) мы знаем, что все блокировки переменных должны быть поставлены в очередь, а другим потокам не разрешено разблокировать объекты, заблокированные текущим потоком, поэтому синхронизация выполняется упорядоченно.

Подводя итог, синхронизация может гарантировать атомарность, видимость и упорядоченность.

Справедливая блокировка против несправедливой блокировки

Благодаря приведенному выше обучению мы знаем принцип реализации синхронизированного, и он является реентерабельным, тогда является ли это справедливой блокировкой?

Подавать напрямую:

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.

Тем не менее, фактическая операция фактической операции не будет отображаться выше, поэтому SYNCHRONIZED — это блокировка без сбоев.

оптимизация блокировки

Java постоянно развивается, и аналогично развиваются такие древние вещи, как синхронизация в Java.Например, ConcurrentHashMap был еще заблокирован ReentrantLock в jdk7, а в jdk8 заменен нативной синхронизацией.Видно, что синхронизировано с нативной поддержкой , ему еще есть куда развиваться.

Итак, каковы эволюционирующие состояния синхронизированного?

Давайте кратко представим это здесь:

(1) Предвзятая блокировка означает, что часть кода синхронизации всегда доступна потоку, после чего этот поток автоматически получает блокировку, снижая стоимость получения блокировки.

(2) Облегченная блокировка означает, что когда блокировка является смещенной блокировкой и к ней обращается другой поток, смещенная блокировка будет обновлена ​​до облегченной блокировки. Этот поток попытается получить блокировку, вращаясь без блокировки. Повышение производительности.

(3) Тяжеловесная блокировка означает, что если замок является облегченным замком, то после того, как вращающаяся нить сделает определенное количество оборотов, она войдет в состояние блокировки, и замок будет обновлен до тяжеловесного замка без получения блокировки. заблокирует другие потоки и снизит производительность.

Суммировать

(1) synchronized генерирует инструкции байт-кода monitorenter и monitorexit до и после блока synchronized во время компиляции;

(2) Инструкции байт-кода monitorenter и monitorexit требуют параметр ссылочного типа, а базовый тип не может;

(3) Нижний уровень инструкций байт-кода monitorenter и monitorexit — это инструкции блокировки и разблокировки, использующие модель памяти Java;

(4) синхронизированная блокировка с повторным входом;

(5) синхронизированный — это несправедливая блокировка;

(6) синхронизация может обеспечить атомарность, видимость и упорядоченность одновременно;

(7) Существует три состояния синхронизации: смещенная блокировка, облегченная блокировка и усиленная блокировка;

Пасхальные яйца — пять способов использования синхронизированных

Благодаря приведенному выше анализу мы знаем, что для 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.

Кроме того, множественные синхронизированные блокируют только один и тот же объект, а код между ними синхронизируется, на это необходимо обращать внимание при использовании синхронизированных.

Рекомендуемое чтение

  1. JMM (модель памяти Java) из мертвой серии синхронизации Java

  2. Неустойчивый анализ мертвой серии синхронизации Java


Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись «Брат Тонг читает исходный код», проверить больше статей из серии исходного кода и поплавать в океане исходного кода с братом Тонгом.

qrcode