Метка: статья в публичном аккаунте "Мы все маленькие лягушки"
Condition
Внутренняя реализация ReentrantLock
закончить смотретьAQS
Базовый механизм синхронизации в , давайте кратко проанализируем представленный ранееReentrantLock
принцип реализации. Давайте рассмотрим типичное использование этой явной блокировки:
Lock lock = new ReentrantLock();
lock.lock();
try {
加锁后的代码
} finally {
lock.unlock();
}
ReentrantLock
Первая — это явная блокировка, которая реализуетLock
интерфейс. Может быть, вы забылиLock
Как выглядит интерфейс, рассмотрим его еще раз:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
фактическиReentrantLock
внутренне определяетAQS
подкласс, чтобы помочь ему реализовать функцию блокировки, потому чтоReentrantLock
работает в独占模式
вниз, так чтоlock
метод на самом деле вызываетAQS
объектaquire
метод получения состояния синхронизации,unlock
метод на самом деле вызываетAQS
объектrelease
Способ выхода из состояния синхронизации, они уже всем знакомы, поэтому повторяться не буду, давайте в общих чертахReentrantLock
код:
public class ReentrantLock implements Lock {
private final Sync sync; //AQS子类对象
abstract static class Sync extends AbstractQueuedSynchronizer {
// ... 为节省篇幅,省略其他内容
}
// ... 为节省篇幅,省略其他内容
}
Итак, если мы просто напишем следующую строку кода:
Lock lock = new ReentrantLock();
Это означает созданиеReentrantLock
объект, аAQS
объект, вAQS
объект поддерживается同步队列
изhead
узел иtail
узел, но в начальном состоянии, так как нет потоков, конкурирующих за блокировки, поэтому同步队列
Он пустой, а рисунок такой:
Предложение условия
Мы упомянули встроенную блокировку, когда говорили о межпоточном взаимодействии.wait/notify
механизм,等待线程
Типичный код выглядит следующим образом:
synchronized (对象) {
处理逻辑(可选)
while(条件不满足) {
对象.wait();
}
处理逻辑(可选)
}
Типичный код потока уведомлений выглядит следующим образом:
synchronized (对象) {
完成条件
对象.notifyAll();、
}
То есть, когда поток не может быть удовлетворен из-за определенного условия, он может вызвать объект блокировки, удерживая блокировку.wait
метод, то поток снимет блокировку и войдет в очередь ожидания, связанную с объектом блокировки, для ожидания; если поток завершает условие ожидания, то вызывает метод блокировки, удерживая ту же блокировкуnotify
илиnotifyAll
Метод пробуждает потоки, ожидающие в очереди ожидания, связанной с этим объектом блокировки.
Суть явной блокировки на самом деле черезAQS
Объект получает и освобождает состояние синхронизации, а реализация встроенной блокировки инкапсулируется в виртуальной машине Java.Мы не говорили, что реализация этих двух различна.. иwait/notify
механизм работает только со встроенными замками, в显式锁
Нам нужно определить другой набор подобных механизмов, и нам нужно сделать это ясным, когда мы определяем этот механизм:Когда поток, который получает блокировку, не соответствует определенному условию, в какую очередь ожидания он должен войти и когда следует снять блокировку?Если поток выполняет условие ожидания, как его можно удалить из соответствующей очереди ожидания, удерживая такая же блокировка? Удалить ожидающий поток из очереди в.
Чтобы определить эту очередь ожидания, дяди, которые разработали java, находятся вAQS
добавлено имяConditionObject
Член внутреннего класса:
public abstract class AbstractQueuedSynchronizer {
public class ConditionObject implements Condition, java.io.Serializable {
private transient Node firstWaiter;
private transient Node lastWaiter;
// ... 为省略篇幅,省略其他方法
}
}
Очевидно, этоConditionObject
поддерживать очередь,firstWaiter
ссылка на головной узел очереди,lastWaiter
является ссылкой на хвостовой узел очереди. Но класс узлаNode
? Да, вы правильно прочитали, это то, что мы проанализировали ранее同步队列
используется вAQS
статический внутренний классNode
, боюсь ты забыл, тогда ставь этоNode
Основное содержимое класса узла записывается снова:
static final class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
}
Это:AQS
Класс узла, используемый очередью синхронизации и настраиваемой очередью ожидания, одинаков..
потому чтоКогда поток в очереди ожидания пробуждается, ему необходимо повторно получить блокировку, то есть повторно получить состояние синхронизации, поэтому очередь ожидания должна знать, какую блокировку удерживает поток, когда он начинает ждать.. Дяди, которые разработали java, находятся вLock
Интерфейс предоставляет такой метод для получения очереди ожидания через блокировку:
Condition newCondition();
мы представили вышеConditionObject
это сбылосьCondition
интерфейс, посмотриReentrantLock
Как блокировка получает связанную с ней очередь ожидания:
public class ReentrantLock implements Lock {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
final ConditionObject newCondition() {
return new ConditionObject();
}
// ... 为节省篇幅,省略其他方法
}
public Condition newCondition() {
return sync.newCondition();
}
// ... 为节省篇幅,省略其他方法
}
Как видите, это просто вопрос созданияConditionObject
только объект~Поскольку ConditionObject является членом внутреннего класса AQS, созданный объект ConditionObject содержит ссылку на объект AQS, поэтому доступ к очереди синхронизации осуществляется через объект ConditionObject, то есть состояние синхронизации может быть повторно получено, то есть блокировка может быть повторно приобретен. Это все еще немного запутанно, чтобы описать словами.Давайте сначала создадим замок.Condition
Объект:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Поскольку в начальном состоянии потоки не конкурируют за блокировки, поэтому同步队列
пуст, и ни один поток не входит в очередь ожидания, потому что определенное условие не выполняется, поэтому等待队列
тоже пусто,ReentrantLock
объект,AQS
Представление объектов и очередей ожидания в памяти показано на рисунке:
Конечно, этоnewCondition
Метод можно вызывать повторно, так что несколько блокировок могут быть сгенерированы с помощью одной блокировки.等待队列
:
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Затем нам нужно подумать, как завернуть нить вNode
Где узлы помещаются в очередь ожидания и как они удаляются из очереди ожидания.ConditionObject
Внутренний класс-член реализуетCondition
Интерфейс, этот интерфейс предоставляет следующие методы:
public interface Condition {
void await() throws InterruptedException;
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void awaitUninterruptibly();
void signal();
void signalAll();
}
Давайте посмотрим на конкретное значение этих методов:
имя метода | описывать |
---|---|
void await() |
Текущий поток переходит в состояние ожидания до тех пор, пока он не будет уведомлен (вызов метода signal или signalAll) или не будет прерван. |
boolean await(long time, TimeUnit unit) |
Текущий поток входит в состояние ожидания в течение заданного времени и возвращается, если он превышает указанное время или получает уведомление или прерывается в состоянии ожидания. |
long awaitNanos(long nanosTimeout) |
То же, что и предыдущий метод, за исключением того, что используемая единица времени по умолчанию — наносекунды. |
boolean awaitUntil(Date deadline) |
Текущий поток входит в состояние ожидания и возвращается, если достигнут крайний срок или если он уведомлен или прерван в состоянии ожидания. |
void awaitUninterruptibly() |
Текущий поток переходит в состояние ожидания до тех пор, пока он не будет уведомлен в состоянии ожидания.Когда необходимо обратить внимание, этот метод не прерывается соответственно |
void signal() |
Разбудите ожидающий поток. |
void signalAll() |
Разбудить все ожидающие потоки. |
можно увидеть,Condition
серединаawait
методы и встроенные объекты блокировкиwait
Функция метода такая же, она переводит текущий поток в состояние ожидания,signal
методы и встроенные объекты блокировкиnotify
Функция метода такая же, он разбудит потоки в очереди ожидания.
как вызов встроенного замкаwait/notify
метод, поток должен сначала получить блокировку, например, вызовCondition
объектawait/siganl
Поток метода должен сначала получить поток, породившийCondition
Явная блокировка объекта. Его основное использование заключается в следующем:Генерируется методом newCondition явной блокировкиCondition
объект, поток может вызвать сгенерированную блокировку, удерживая явную блокировкуCondition
методы ожидания/сигнала объекта, общее использование выглядит следующим образом:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//等待线程的典型模式
public void conditionAWait() throws InterruptedException {
lock.lock(); //获取锁
try {
while (条件不满足) {
condition.await(); //使线程处于等待状态
}
条件满足后执行的代码;
} finally {
lock.unlock(); //释放锁
}
}
//通知线程的典型模式
public void conditionSignal() throws InterruptedException {
lock.lock(); //获取锁
try {
完成条件;
condition.signalAll(); //唤醒处于等待状态的线程
} finally {
lock.unlock(); //释放锁
}
}
Предположим, что теперь есть блокировка и две очереди ожидания:
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
На чертеже видно, что:
Есть 3 темыmain
,t1
,t2
звонить в то же времяReentrantLock
объектlock
Если метод конкурирует за блокировку, только потокmain
Блокировка получена, поэтому нить будетt1
,t2
упаковано вNode
Вставка узла同步队列
,такReentrantLock
объект,AQS
объект и同步队列
Схема выглядит так:
потому что в это времяmain
Поток получает блокировку и находится в рабочем состоянии, но, поскольку определенное условие не выполняется, он выбирает выполнение приведенного ниже кода для входа.condition1
очередь ожидания:
lock.lock();
try {
contition1.await();
} finally {
lock.unlock();
}
конкретныйawait
Мы не будем анализировать код, он слишком длинный, боюсь, вы заснете, просто посмотрите вот этоawait
Что делает метод:
-
существует
condition1
Ожидание создания очередиNode
узел, этот узелthread
значениеmain
нить иwaitStatus
за-2
, статическая переменнаяNode.CONDITION
, указывающий, что узел находится в очереди ожидания, поскольку этот узел является репрезентативным потокомmain
, так это называетсяmain节点
Проще говоря, вновь созданный узел выглядит так: -
вставьте этот узел
condition1
Ожидание в очереди: -
так как
main
Поток также удерживает блокировку, поэтому необходимо уведомить поток, ожидающий получения блокировки, после снятия блокировки.t
,так同步队列
Узел 0 в удален, потокt
получить замок,节点1
называетсяhead
узел и поставитьthread
Поле имеет значение null:
Уже,main
Операция ожидания потока завершена, если блокировка получена сейчасt1
Поток также выполняет следующий код:
lock.lock();
try {
contition1.await();
} finally {
lock.unlock();
}
Вышеупомянутый процесс все еще будет выполняться, иt1
Нитки упакованы какNode
узел вставляется вcondition1
Ожидание выхода в очередь, потому что оригинал в очереди ожидания节点1
будет удален, мы помещаем эту новую вставку в очередь ожидания от имени потокаt1
узел называется新节点1
Бар:
Особое внимание здесь:Очередь синхронизации представляет собой двусвязный список, prev представляет собой предыдущий узел, next представляет следующий узел, а очередь ожидания представляет собой односвязный список, использующий nextWaiter для представления следующего узла, здесь они различаются..
Поток, получивший блокировку, теперьt2
, все вышли смешать, первые двое зашли, только остальныеt2
Как плохо, но не в этот разcondition1
Конец очереди, замените его наcondition2
Ставьте в очередь:
lock.lock();
try {
contition2.await();
} finally {
lock.unlock();
}
Эффект:
Все обнаружили, что хотя ни один поток не получает блокировку, и ни один поток не ожидает блокировки, но同步队列
В нем еще есть узел, да,Очередь синхронизации пуста только тогда, когда ни один поток не заблокирован из-за блокировки в начале Пока есть поток, заблокированный из-за невозможности получения блокировки, очередь не пуста..
Уже,main
,t1
иt2
Эти три потока вошли в состояние ожидания, кто их выведет, когда они все будут заняты? ? ? Ну~ Хорошо, давайте заставим другой поток получить ту же блокировку, например, потокt3
Перейти кcondition2
Пробуждение потока условной очереди, вы можете вызвать этоsignal
метод:
lock.lock();
try {
contition2.signal();
} finally {
lock.unlock();
}
Так какcondition2
Потоки, ожидающие в очереди, толькоt2
,такt2
будет разбужен, и этот процесс выполняется в два этапа:
-
будет
condition2
Репрезентативный поток, ожидающий очередиt2
из新节点2
, удален из очереди ожидания. -
будет удален
节点2
Поместите его в очередь синхронизации и подождите получения блокировки, одновременно меняя узелwaitStauts
за0
.
Иллюстрация этого процесса выглядит следующим образом:
если нитьt3
Продолжай звонитьsignalAll
Пучокcondition1
Ожидание пробуждения потока в очереди аналогично, ноcondition1
Два узла на обоих перемещаются в очередь синхронизации одновременно:
lock.lock();
try {
contition1.signalAll();
} finally {
lock.unlock();
}
Эффект показан на рисунке:
Таким образом, все потоки начинаются с等待
Состояние восстановилось, и блокировка может быть повторно задействована для следующей операции.
ВышеупомянутоеCondition
Принцип и использование механизма, фактически это встроенный замок.wait/notify
Другая реализация механизма в явной блокировке, ноИсходный встроенный объект блокировки может соответствовать только одной очереди ожидания.Теперь явная блокировка может генерировать несколько очередей ожидания.Мы можем размещать потоки в разных очередях ожидания в соответствии с разными условиями ожидания потоков..Condition
Назначение механизма может относиться кwait/notify
механизм, далее мы используем встроенный замок иwait/notify
Синхронизированная очередь, написанная механизмомBlockedQueue
использовать显式锁 + Condition
способ написать:
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionBlockedQueue<E> {
private Lock lock = new ReentrantLock();
private Condition notEmptyCondition = lock.newCondition();
private Condition notFullCondition = lock.newCondition();
private Queue<E> queue = new LinkedList<>();
private int limit;
public ConditionBlockedQueue(int limit) {
this.limit = limit;
}
public int size() {
lock.lock();
try {
return queue.size();
} finally {
lock.unlock();
}
}
public boolean add(E e) throws InterruptedException {
lock.lock();
try {
while (size() >= limit) {
notFullCondition.await();
}
boolean result = queue.add(e);
notEmptyCondition.signal();
return result;
} finally {
lock.unlock();
}
}
public E remove() throws InterruptedException{
lock.lock();
try {
while (size() == 0) {
notEmptyCondition.await();
}
E e = queue.remove();
notFullCondition.signalAll();
return e;
} finally {
lock.unlock();
}
}
}
В этой очереди мы использовалиReentrantLock
блокировка, через которую генерируются две блокировкиCondition
объект,notFullCondition
Указывает условие, что очередь не заполнена,notEmptyCondition
Указывает условие, что очередь не пуста. Когда очередь заполнена, потокnotFullCondition
Ожидание, каждый раз, когда элемент вставляется, он будет уведомлятьnotEmptyCondition
Поток, который условно ожидает; когда очередь пуста, поток будет ждатьnotEmptyCondition
Ожидание, каждый раз, когда элемент удаляется, он будет уведомлятьnotFullCondition
Условно ожидающие потоки. Таким образом, семантика становится очевидной.Если у вас есть больше условий ожидания, вы можете сгенерировать больше с помощью явной блокировки.Condition
объект. иКаждый встроенный объект блокировки может иметь только одну связанную очередь ожидания, что является одним из преимуществ явных блокировок по сравнению со встроенными блокировками..
Подведем итог использования выше:Каждый объект явной блокировки может генерировать несколькоCondition
объект, каждыйCondition
Объект будет соответствовать очереди ожидания, поэтому он имеет эффект явной блокировки, соответствующей нескольким очередям ожидания..
AQS
Другие важные методы для ожидающих очередей в
КромеCondition
объектawait
иsignal
метод,AQS
Существует также ряд методов прямого доступа к этой очереди, все из которыхpublic final
декоративный:
public abstract class AbstractQueuedSynchronizer {
public final boolean owns(ConditionObject condition)
public final boolean hasWaiters(ConditionObject condition) {}
public final int getWaitQueueLength(ConditionObject condition) {}
public final Collection<Thread> getWaitingThreads(ConditionObject condition) {}
}
имя метода | описывать |
---|---|
owns |
Проверьте, не является ли этоAQS Указанный объект ConditionObject, сгенерированный объектом |
hasWaiters |
Есть ли ожидающие потоки в указанной очереди ожидания |
getWaitQueueLength |
Возвращает оценку количества потоков, ожидающих выполнения этого условия. Поскольку фактический набор потоков в многопоточной среде может значительно измениться при построении этого результата. |
getWaitingThreads |
Возвращает оценку набора потоков, ожидающих выполнения этого условия. Поскольку фактический набор потоков в многопоточной среде может значительно измениться при построении этого результата. |
При необходимости их можно использовать в нашем пользовательском инструменте синхронизации.
Не по теме
Написание статей очень утомительно, и иногда вы чувствуете, что чтение идет очень гладко, что на самом деле является результатом бесчисленных правок за ним. Если вы думаете, что это хорошо, пожалуйста, помогите переслать его.Большое спасибо~ Вот мой публичный аккаунт "Мы все маленькие лягушки".
буклет
Кроме того, автор также написал буклет MySQL:Ссылка на статью «Как работает MySQL: понимание MySQL у истоков». Содержание буклета в основном представлено с точки зрения Xiaobai, объясняя некоторые основные концепции MySQL на относительно распространенном языке, такие как записи, индексы, страницы, табличные пространства, оптимизация запросов, транзакции и блокировки и т. д. Общее количество слов составляет от 300 000 до 400 000 слов с сотнями оригинальных иллюстраций. Основная цель состоит в том, чтобы облегчить обычным программистам изучение MySQL для продвинутых пользователей и сделать кривую обучения немного более плавной. Студенты, у которых есть сомнения относительно MySQL для продвинутых пользователей, могут взглянуть на: