Требования к собеседованию: анализ реализации условия Java AQS [Глава бутика]

Java
Требования к собеседованию: анализ реализации условия Java AQS [Глава бутика]

Эта статья составлена ​​на основе главы 5 книги «Искусство параллельного программирования на Java». Автор: Фанг Тэнфэй  Вэй Пэн  Чэн Сяомин

AQS:AbstractQueuedSynchronizer

ConditionObject — это внутренний класс AQS синхронизатора.Поскольку операция Condition должна получить связанную блокировку, также разумно быть внутренним классом синхронизатора. Каждый объект «Условие» содержит очередь (далее именуемую очередью ожидания), которая является ключом для объекта «Условие» для реализации функции ожидания/уведомления.

Реализация Condition будет проанализирована ниже, в основном включая: очередь ожидания, ожидание и уведомление.Упомянутое ниже условие относится к ConditionObject, если оно не указано.

1. Очередь ожидания

Очередь ожидания представляет собой очередь FIFO. Каждый узел в очереди содержит ссылку на поток, который представляет собой поток, ожидающий объекта Condition. Если поток вызывает метод Condition.await(), поток освобождает блокировку, создает узел присоединиться к очереди ожидания и войти в состояние ожидания. Фактически определение узла повторно использует определение узла в синхронизаторе, то есть типы узлов в очереди синхронизации и очереди ожидания являются статическим внутренним классом AbstractQueuedSynchronizer.Node синхронизатора.

Условие содержит очередь ожидания, а условие имеет первый узел (firstWaiter) и хвостовой узел (lastWaiter). Текущий поток вызывает метод Condition.await(), который создаст узел с текущим потоком и добавит узел в очередь ожидания из хвоста.Базовая структура очереди ожидания показана на рисунке 5-9.

Как показано на рисунке, условие имеет ссылку на головной и хвостовой узлы, и новому узлу нужно только указать исходный хвостовой узел nextWaiter на него и обновить хвостовой узел. Приведенный выше процесс обновления ссылки на узел не использует гарантию CAS, поскольку поток, вызывающий метод await(), должен быть потоком, получившим блокировку, а это означает, что блокировка гарантирует потокобезопасность процесса.

В мониторной модели Объекта объект имеет очередь синхронизации и очередь ожидания, а Блокировка (точнее, синхронизатор) в параллельном пакете имеет очередь синхронизации и несколько очередей ожидания, и соответствующая взаимосвязь показана на рисунке 5. - 10 показано.

2. Подождите

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

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

Метод await() для Condition выглядит следующим образом:

public final void await() throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 当前线程加入等待队列
        Node node = addConditionWaiter();
        // 释放同步状态,也就是释放锁
        int savedState = fullyRelease(node);
        int interruptMode = 0;
        while (!isOnSyncQueue(node)) {
            LockSupport.park(this);
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
        }
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        if (node.nextWaiter != null)
            unlinkCancelledWaiters();
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
    }

Поток, вызывающий этот метод, успешно получает поток блокировки, который является первым узлом в очереди синхронизации.Этот метод создаст текущий поток в узел и добавит его в очередь ожидания, затем сбросит состояние синхронизации, пробудит узел-преемник в очередь синхронизации, а затем текущий поток перейдет в состояние ожидания.

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

С точки зрения очереди текущий поток присоединяется к ожидающей очереди условия, и этот процесс показан на рис. 5-11.

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

3. Уведомление

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

Метод signal() условия, как показано в листинге 5-23.

Листинг 5-23. Сигнальный метод объекта ConditionObject.

 public final void signal() {
        //isHeldExclusively() AQS 子类实现
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        Node first = firstWaiter;
        if (first != null)
            doSignal(first);
    }

Предварительным условием для вызова этого метода является то, что текущий поток должен получить блокировку.Вы можете видеть, что метод signal() был проверен isHeldExclusively(), то есть текущий поток должен быть потоком, который получил блокировку. Затем получите головной узел очереди ожидания, переместите его в очередь синхронизации и используйте LockSupport, чтобы разбудить поток в узле.

Процесс перемещения узла из очереди ожидания в очередь синхронизации показан на рис. 5-12.

При вызове метода синхронизатора enq(Node node) поток головного узла в очереди ожидания безопасно перемещается в очередь синхронизации. Когда узел перемещается в очередь синхронизации, текущий поток использует LockSupport для пробуждения потока узла.

Пробужденный поток выйдет из цикла while в методе await() (метод isOnSyncQueue(Node node) возвращает true, узел уже находится в очереди на синхронизацию), а затем вызовет метод acceptQueued() синхронизатора, чтобы присоединиться к нему для получения состояние синхронизации в соревновании.

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

Метод signalAll() в Condition эквивалентен однократному выполнению метода signal() для каждого узла в очереди ожидания, что приводит к перемещению всех узлов из очереди ожидания в очередь синхронизации и пробуждению потока каждого узла.

4. Информация о блогере

Личный публичный аккаунт WeChat:

личный блог

личный гитхаб

Личный блог самородков

Личный блог CSDN