Введение
Предыдущий«ReentrantLock блокировки Java (1)»Представлен базовый исходный код ReentrantLock, проанализирован механизм справедливой и нечестной блокировки ReentrantLock, а окончательный анализ ReentrantLock по-прежнему зависит от
AbstractQueuedSynchronizerОчередь синхронизации (далее синхронизатор) реализована, поэтому в этой статье начинается анализ реализации кода внутри синхронизатора.Учитывая, что структура кода относительно длинная, маловажный код будет упрощен при анализе исходного кода, но в в конце концов это не повлияет на код.упрощается логически.
2. Анализ синхронизатора
-
Основные свойства синхронизатора
Согласно приведенному выше исходному коду, мы можем знать, что
AbstractQueuedSynchronizerОбъект узла Node строится внутри, а головной узел и хвостовой узел с изменчивыми атрибутами строятся одновременно, чтобы обеспечить видимость между несколькими потоками.В то же время самое важное - определить состояние переменной типа int, который проанализирован в предыдущей статье. , мы знаем, что решение о том, получил ли ReenTrantLock блокировку, заключается в том, является ли состояние больше 0, равное 0 означает, что блокировка простаивает, а больше 0 означает, что блокировка была получена. Далее мы сосредоточимся на анализе внутренней структуры узла Node и принципа реализации синхронизатора.Исходный код узла выглядит следующим образом:
static final class Node {
//共享模式
static final Node SHARED = new Node();
//独占模式
static final Node EXCLUSIVE = null;
//取消状态
static final int CANCELLED = 1;
//唤醒状态
static final int SIGNAL = -1;
//等待条件状态
static final int CONDITION = -2;
//传播状态
static final int PROPAGATE = -3;
//等待状态
volatile int waitStatus;
//上一个节点
volatile Node prev;
//下一个节点
volatile Node next;
//获取同步状态的线程
volatile Thread thread;
//等待队列中的后继节点,如果当前节点是共享的,那么这个nextWaiter=SHARED
Node nextWaiter;
//判断当前后继节点是否是共享的
final boolean isShared() {
return nextWaiter == SHARED;
}
//返回当前节点的前一个节点
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
Здесь необходимо выделить следующие свойства:
waitStatus, состояние включает в себя несколько констант, объявленных внутри узла, а именно:
| постоянное имя | Функции |
|---|---|
| CANCELLED | Если значение равно 1, текущий узел переходит в отмененное состояние. Причина в том, что он входит в отмененное состояние из-за прерывания или ожидания тайм-аута. Следует отметить, что после того, как поток узла перейдет в отмененное состояние, состояние будет не изменится, и получение блокировки не будет заблокировано. |
| SIGNAL | Если значение равно -1, поток узла-преемника находится в состоянии ожидания, и если поток текущего узла освобождает состояние синхронизации или отменяется, он уведомляет узел-преемник, так что поток узла-преемника может бежать |
| CONDITION | Значение равно -2, узел находится в очереди ожидания, а поток узла ожидает выполнения условия. Когда другие потоки вызывают метод signal() для условия, узел будет перемещен из очереди ожидания в очередь синхронизации. и добавлено состояние синхронизации.в соревновании |
| PROPAGATE | Значение -3 указывает, что следующее получение общего состояния синхронизации будет распространяться безоговорочно.Например, если головной узел получает общее состояние синхронизации и определяет, что это состояние РАСПРОСТРАНЕНИЕ, он продолжит вызывать doReleaseShared, чтобы последующие узлы продолжали получить замок. |
| INITIAL | Значение равно 0, что указывает на начальное состояние (это должно быть в коде в старой версии. В настоящее время jdk1.8 не показывает объявление начального состояния, потому что переменная int по умолчанию равна 0 во время инициализации) |
Анализируя свойства синхронизатора, мы можем примерно начертить принципиальную схему очереди конструктора следующим образом:
Во-первых, синхронизатор объявляет головной узел и хвостовой узел.Головной узел указывает на узел узла, указывая, что узел является головным узлом очереди, а хвостовой узел указывает на узел узла, указывая, что узел является узлом. хвостовой узел В то же время каждый узел имеет атрибуты pre и next, указывает на узел node, а затем строит очередь двусвязного списка FIFO, как показано на рисунке. Давайте рассмотрим основные методы, обычно используемые синхронизатором.
-
Список основных методов синхронизатора
| Имя метода | Функции |
|---|---|
| compareAndSetState(int expect, int update) | CAS для установки статуса синхронизации |
| enq(final Node node) | Цикл в очереди ожидания, пока очередь не будет успешной |
| addWaiter(Node mode) | Создайте хвостовой узел с текущим потоком и добавьте его в хвост |
| unparkSuccessor(Node node) | Разбудить преемника узла |
| doReleaseShared() | Отпустите состояние синхронизации в режиме обмена |
| setHeadAndPropagate(Node node, int propagate) | Установите узел головки и продолжайте распространять лицензию синхронизации |
| release(int arg) | Эксклюзивное состояние синхронизации выпуска |
| acquireShared(int arg) | Общее состояние синхронизации выпуска |
| hasQueuedPredecessors() | Определить, есть ли поток, ожидающий дольше, чем текущий поток (для справедливых блокировок). |
-
Блокировка синхронизатора
Выше приведены основные методы синхронизатора. Мы сосредоточимся на анализе некоторых из вышеперечисленных методов. Чтобы понять, как синхронизатор завершает блокировку, ожидает получения блокировки и освобождает блокировку, мы сначала рассмотрим предыдущую статью, чтобы проанализировать метод Lock(), исходный код выглядит следующим образом:
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
Мы обнаружили, что основное внимание уделяется методу accept(1), который предоставляется родительским классом, то есть синхронизатором.Исходный код выглядит следующим образом:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
Исходный код фактически можно разделить на три части:
-
tryAcquire(arg)попробуй получить замок -
addWaiter(Node.EXCLUSIVE), arg)Создайте узел с текущим потоком и добавьте его в конец очереди -
acquireQueued(final Node node, int arg)Пусть узел получает состояние синхронизации в бесконечном цикле и выходит из цикла, если это удается
На самом деле, функция этого метода очень ясна, если разбить его на три части. Во-первых, попытаться получить блокировку. Если вы не можете ее получить, добавьте себя в хвост, а затем выполните цикл в очереди, чтобы получить замок. Самая важная часть
acquireQueued(final Node node, int arg), исходный код выглядит следующим образом:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取节点的前任节点
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) { //如果前任节点是头节点,
//并且当前节点获取到了锁,
//也就是说队列头节点释放了锁,同时把当前节点设置为头节点
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//判断当前节点是否应该被阻塞,那么就把当前线程阻塞挂起,防止无谓的死循环
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//该方法主要靠前驱节点判断当前线程是否应该被阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* 如果前任节点的状态等于SIGNAL,
* 说明前任节点获取到了同步状态,当前节点应该被阻塞,返回true
*/
return true;
if (ws > 0) {
/*
* 前任节点被取消
*/
do {//循环查找取消节点的前任节点,
//直到找到不是取消状态的节点,然后剔除是取消状态的节点,
//关联前任节点的下一个节点为当前节点
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* CAS设置前任节点等待状态为SIGNAL,
* 设置成功表示当前节点应该被阻塞,下一次循环调用就会
* return true
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//把当前线程挂起,从而阻塞住线程的调用栈,
//同时返回当前线程的中断状态。
//其内部则是调用LockSupport工具类的park()方法来阻塞该方法。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//阻塞线程
return Thread.interrupted();
}
Выше приведен процесс блокировки lock.lock(), резюмируем:
- Во-первых, попробуйте получить блокировку напрямую, и получение завершится сразу.
- Если получение блокировки не удается, создайте хвостовой узел для текущего потока и добавьте его в конец очереди в режиме CAS.
- В очереди бесконечный цикл определяет, является ли предшествующий узел головным узлом. Если это головной узел, он пытается получить блокировку. Если нет, он приостанавливается и ждет, пока предшествующий узел проснется. можно избежать бесконечного цикла нескольких потоков потребление производительности.
-
Синхронизатор разблокирован
lock.unlock() Процесс снятия блокировки, анализ исходного кода, старые правила, исходный код:
//释放锁
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//尝试释放锁
if (tryRelease(arg)) {
Node h = head;
//如果头节点不为空,并且不是初始状态,也就是不在队列中了
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//唤醒后继节点
return true;
}
return false;
}
//该方法和之前分析的代码类似,主要是设置Sate状态
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);//设置同步状态占有线程为null
}
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 取到当前节点的下一个节点,如果节点不为空就唤醒阻塞的线程
//如果节点为空,或者节点是取消状态,那么就循环从尾部节点找到
//当前节点的下一个节点唤醒
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);//唤醒阻塞的线程
}
Выше приведен анализ lock.unlock(), а также мы суммируем и анализируем
- Поскольку первый судья блокировки Успех состояния не составляет 0, поэтому состояние будет отпустить блокировку установлено значение 0, а замок потока владельца устанавливается на NULL
- Блокировка снимается успешно, а затем последующие узлы в очереди будут разбужены вызовом
LockSupport.unpark(s.thread)разбудить нить,LockSupportв основном полагаться наsun.misc.UnsafeОн реализуется классом, который предоставляет методы на аппаратном уровне операционной системы и в данной статье не обсуждается.
В-третьих, вывод
- На этот раз я в основном анализирую реализацию блокировки и разблокировки синхронизатора AQS.На самом деле, многие классы инструментов синхронизации jdk реализованы синхронизатором AQS.После понимания принципа синхронизатора AQS также очень полезно понять принципы других параллельные инструменты. полезные, такие как
CountDownLatch,Semaphore,CyclicBarrier(依赖ReentrantLock)Ждать. Следующая статья «ReentrantReadWriteLock для блокировок Java» продолжает уточнять анализ, анализирует блокировки чтения и записи, а также анализирует, как ReentrantLock реализует такие функции, как повторное получение блокировок чтения и ухудшение блокировки.