Параллелизм Java — синхронизированный анализ ключевых слов

Java задняя часть JVM Java EE

синхронизированное использование

В Java самым простым и грубым средством синхронизации является ключевое слово synchronized, которое можно использовать тремя способами:
① Метод синхронизированного экземпляра, блокировка — это текущий объект экземпляра.
② Синхронизированный метод класса, блокировка — это текущий объект класса.
③ Синхронизированный кодовый блок, замок — объект в скобках

Пример:


public class SynchronizedTest {

    /**
     * 同步实例方法,锁实例对象
     */
    public synchronized void test() {
    }

    /**
     * 同步类方法,锁类对象
     */
    public synchronized static void test1() {
    }

    /**
     * 同步代码块
     */
    public void test2() {
        // 锁类对象
        synchronized (SynchronizedTest.class) {
            // 锁实例对象
            synchronized (this) {

            }
        }
    }
}

синхронизированная реализация

javap -verbose, чтобы увидеть приведенный выше пример:

Из рисунка мы видим, что:
Синхронный метод: синхронизация на уровне метода не контролируется инструкциями байт-кода, она реализована в операциях вызова и возврата метода. При вызове метода вызывающая инструкция проверяет, установлен ли флаг доступа к методу ACC_SYNCHRONIZED. Если он установлен, поток выполнения должен удерживать монитор для запуска метода. Когда метод завершен (независимо от того, возникает ли исключение ), монитор освобождается.

Блок синхронизированного кода: после компиляции ключевого слова synchronized две инструкции байт-кода, monitorenter и monitorexit, будут сформированы до и после синхронизированного блока.Каждая инструкция monitorenter должна выполнять соответствующую инструкцию monitorexit.Чтобы гарантировать, что две инструкции остаются, когда метод ненормально завершено Если он может быть выполнен правильно, компилятор автоматически сгенерирует обработчик исключения, целью которого является выполнение инструкции monitorexit (14-18 и 24-30 на рисунке - это процессы исключения).

Посмотрите, что делает команда monitorexit, найдите monitorenter в исходном коде Hotspot и найдите следующее в ciTypeFlow.cpp:


    case Bytecodes::_monitorenter:
    {
      pop_object();
      set_monitor_count(monitor_count() + 1);
      break;
    }
  case Bytecodes::_monitorexit:
    {
      pop_object();
      assert(monitor_count() > 0, "must be a monitor to exit from");
      set_monitor_count(monitor_count() - 1);
      break;
    }
    
    void      pop_object() {
      assert(is_reference(type_at_tos()), "must be reference type");
      pop();
    }
    
    void      pop() {
      debug_only(set_type_at_tos(bottom_type()));
      _stack_size--;
    }
    int         monitor_count() const  { return _monitor_count; }
    void    set_monitor_count(int mc)  { _monitor_count = mc; }

Из исходного кода мы нашлиКогда поток получает блокировку объекта, счетчик увеличивается на единицу, а освобождение блокировки уменьшает счетчик на единицу..

Monitor

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

Монитор — это структура данных, приватная для потока, каждый поток имеет список доступных записей монитора, и в то же время Существует также глобальный доступный список. Каждый заблокированный объект будет связан с монитором (LockWord в MarkWord заголовка объекта указывает на начальный адрес монитора), и в мониторе есть поле владельца для хранения уникального идентификатора потока, которому принадлежит блокировка. , указывая на то, что блокировка принадлежит данному потоку. Его структура выглядит следующим образом:

Owner: изначально NULL означает, что ни один поток в настоящее время не владеет записью монитора.Когда поток успешно владеет блокировкой, уникальный идентификатор потока сохраняется и устанавливается в NULL при снятии блокировки.
EntryQ: связать системный мьютекс (семафор) для блокировки всех потоков, которые не могут заблокировать запись монитора.
RcThis: Указывает количество всех потоков, заблокированных или ожидающих записи монитора.
Nest: счетчик, используемый для реализации повторных блокировок.
HashCode: сохранить значение HashCode, скопированное из заголовка объекта (может также включать возраст GC).
Candidate: используется, чтобы избежать ненужной блокировки или ожидания пробуждения потоков, поскольку только один поток может успешно владеть блокировкой в ​​каждый момент времени.Если предыдущий поток, освободивший блокировку, пробуждает все потоки, которые блокируются или ожидают, переключение контекста (с блокировки на готовность, а затем снова блокировка из-за неудачной блокировки блокировки), что приводит к серьезному снижению производительности. Кандидат имеет только два возможных значения: 0 означает, что нет потока для пробуждения, 1 означает, что нужно разбудить поток-преемник для борьбы за блокировку.

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

В реализации синхронизированного в jdk1.6 были выполнены различные оптимизации, такие как адаптивное вращение, устранение блокировки, огрубление блокировки, облегченная блокировка и блокировка со смещением, в основном для решения трех сценариев:
① В критическую секцию входит только одна нить, смещенная в сторону замка
② Многопоточность вне конкуренции, легкая блокировка
③ Многопоточная конкуренция, сверхтяжелая блокировка
Блокировка смещения→легкая блокировка→тяжелая блокировка,Замки можно улучшать, но нельзя понижать, эта стратегия предназначена для повышения эффективности получения и снятия блокировок, анализ исходного кода может видетьАккаунт для волка - синхронизированная реализация

Блокировка смещения

Цель введения предвзятых блокировок — свести к минимуму ненужные пути выполнения легковесных блокировок без конкуренции многопоточности. По сравнению с облегченными блокировками, смещенные блокировки полагаются только на одну атомарную инструкцию CAS для замены ThreadID. Однако, как только несколько потоков конкурируют, смещенная блокировка должна быть отозвана. Основная проверка заключается в том, является ли это смещенной блокировкой, битом идентификации блокировки и ThreadID.

  • замок
  • ①.Получите слово Mark в заголовке объекта.
    ② Определить, находится ли слово метки в отклоняемом состоянии, то есть флаг смещения метки равен 1, а флаг блокировки равен 01.
    ③ Если он находится в смещенном состоянии, оцените, является ли идентификатор потока в Mark Word идентификатором текущего потока, если он указывает на текущий поток, выполните ⑥, в противном случае выполните ④
    ④ Соревнуйтесь с блокировкой через операцию CAS, если соревнование прошло успешно, замените идентификатор потока Mark Word на текущий идентификатор потока, в противном случае выполните ⑤
    ⑤. Неспособность конкурировать за блокировку через CAS доказывает, что в настоящее время существует многопоточная конкуренция. Когда достигнута глобальная точка безопасности safepoint (На данный момент код не выполняется), поток, получивший смещенную блокировку, приостанавливается, смещенная блокировка отменяется и обновляется до облегченной.После завершения обновления поток, заблокированный в безопасной точке, продолжает выполнять блок синхронизированного кода.
    ⑥ Выполнение блока синхронного кода

  • разблокировать
  • Поток не будет активно освобождать смещенную блокировку. Только когда другие потоки попытаются конкурировать за смещенную блокировку, поток, удерживающий смещенную блокировку, снимет блокировку, и освобождение блокировки должно дождаться глобальной точки безопасности. Действуйте следующим образом:
    ① Подвесьте нить со смещенным замком, чтобы определить, заблокирован ли камень объекта замка.
    ② Отменить смещенную блокировку и вернуться в состояние без блокировки (01) или в состояние облегченной блокировки (00).

    Легкий замок

    Основная цель введения облегченных блокировок — снизить потребление производительности традиционными тяжеловесными блокировками с использованием мьютексов операционной системы при условии отсутствия многопоточной конкуренции. Если несколько потоков войдут в критическую секцию одновременно, это приведет к расширению облегченных блокировок для апгрейда тяжеловесных блокировок, поэтому появление облегченных блокировок не заменит тяжеловесные блокировки.В случае многопоточной конкуренции облегченная блокировка работает медленнее, чем блокировка уровня веса..

  • замок
  • ①.Получите слово Mark в заголовке объекта.
    ② Определить, находится ли текущий объект в свободном от блокировки состоянии, то есть флаг смещенной блокировки метки равен 0, а флаг блокировки равен 01.
    3. Если это так, JVM сначала создаст пространство с именем Lock Record в кадре стека текущего потока для хранения копии текущего слова Mark объекта блокировки (официально добавьте к этой копии префикс Displaced, то есть Displaced). Пометить слово), а затем выполнить ④; если нет, выполнить ⑤
    4.jvm Попробуйте обновить метки объекта Mark, чтобы указать на запись блокировки, если успешно выражена конкуренция на блокировку, бит флаг блокировки выполнен в 00 (указывает на то, что этот объект находится в легком состоянии блокировки), выполните операции синхронизации, если она не удается , он выполнен.
    ⑤. Determine whether the Mark Word of the current object points to the stack frame of the current thread. If it means that the current thread already holds the lock of this object, execute the synchronization code block directly; otherwise, it means that the lock object has been preempted by other threads. If two or more threads compete for the same lock, the lightweight lock will no longer be valid. To expand into a heavyweight lock, the lock flag will become 10, and the waiting thread will enter the blocking государство.

  • разблокировать
  • Разблокировка облегченного замка также осуществляется посредством операции CAS.Основные этапы заключаются в следующем:

    ① Если слово метки объекта по-прежнему указывает на запись блокировки потока, выполните ②
    ②.Используйте операцию CAS для замены текущего слова метки объекта и слова метки смещения, скопированного в потоке.Если это успешно, это означает, что блокировка успешно снята, в противном случае выполните ③
    ③ Если замена операции CAS не удалась, это означает, что есть другие потоки, пытающиеся получить блокировку, и вам нужно разбудить приостановленный поток, освобождая блокировку.

    тяжелый замок

    Тяжеловесная блокировка реализуется монитором внутри объекта. Суть монитора в том, чтобы полагаться на реализацию Mutex Lock базовой операционной системы. Переключение между потоками операционной системы требует переключения из пользовательского режима в режим ядра, а стоимость переключения очень высока.

    благодарный

    Исходный код Taro - синхронизированный принцип реализации
    "Глубокое понимание виртуальной машины Java"
    Искусство параллельного программирования на Java