Эта серия статей размещена в публичном аккаунте [Ccww Technology Blog], а оригинальные технические статьи были запущены раньше, чем блог
предисловие
В интервью много времени интервьюеры будут задать проблему блокировки, ReentrantLock также является точкой, но что будет предложено? Соберите несколько вопросов онлайн:
- Что такое реентерабельная блокировка?
- Что такое честные и нечестные блокировки? Какая разница?
-
ReentrantLock::lock Режим честной блокировки Реальность
- Как ReentrantLock реализует справедливую блокировку?
- Как ReentrantLock обеспечивает повторный вход?
- В чем разница между режимом честной блокировки ReentrantLock и блокировкой получения недобросовестной блокировки?
- ReentrantLock::unlock() снимает блокировку, как разбудить потоки в очереди ожидания?
- Какие еще функции есть у ReentrantLock помимо реентерабельности?
- Разница между ReentrantLock и Synchronized
- Сценарии использования ReentrantLock
Так что же такое реентерабельная блокировка? в чем смысл
Что такое ReentrantLock?
ReentrantLock — типичный монопольный режим AQS, когда состояние синхронизации равно 0, это означает бездействие. Когда поток получает состояние синхронизации бездействия, он увеличивает состояние синхронизации на 1 и меняет состояние синхронизации на небездействующее, поэтому другие потоки приостанавливаются и ждут. При изменении состояния синхронизации записывать собственный поток как основу для последующего повторного входа, то есть, когда поток удерживает блокировку объекта, он может снова получить блокировку этого объекта. Если это блокировка без повторного входа, это вызовет взаимоблокировку.
ReentrantLock будет включать честные и нечестные блокировки, а ключ к реализации лежит в переменных-членах.syncРеализация блокировки отличается, что является ядром блокировки для достижения синхронизации взаимного исключения.
//公平锁和非公平锁的变量
private final Sync sync;
//父类
abstract static class Sync extends AbstractQueuedSynchronizer {}
//公平锁子类
static final class FairSync extends Sync {}
//非公平锁子类
static final class NonfairSync extends Sync {}
Что такое честные и нечестные блокировки? Какая разница?
Что такое честные и нечестные блокировки? Какая разница?
Fair Lock означает, что когда блокировка доступен, нить, которая ждет самого длинного на замке, получит право использовать замок, то есть в первую очередь, в первую очередь. Несчетные блокировки случайным образом назначают право на использование, что представляет собой механизм вытеснения, который приобретает блокировки случайным образом, а не первым, кто сначала заблокирует замок.
ReentrantLock предоставляет конструктор, который может реализовывать честные или несправедливые блокировки:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Несмотря на то, что честные блокировки гарантируются по справедливости, поскольку получение справедливых блокировок не принимает во внимание планирование потоков операционной системой и другие факторы, это повлияет на производительность.
Несмотря на то, что нечестный режим более эффективен, если имеется достаточно потоков, подающих заявку на блокировку в нечестном режиме, некоторые потоки могут не получить блокировку в течение длительного времени, что является проблемой «голодания» несправедливой блокировки.
Но в большинстве случаев мы используем нечестные блокировки, потому что их производительность намного лучше, чем честные блокировки. Но справедливые блокировки могут предотвратить голодание потока и в некоторых случаях полезны.
Далее рассмотрим реализацию честной блокировки ReentrantLock:
ReentrantLock::lock Реализация режима справедливой блокировки
Сначала вам нужно передать функцию сборкиtrueСоздать честный замок
ReentrantLock reentrantLock = new ReentrantLock(true);
перечислитьlock()запирать, напрямуюacquire(1)заблокирован
public void lock() {
// 调用的sync的子类FairSync的lock()方法:ReentrantLock.FairSync.lock()
sync.lock();
}
final void lock() {
// 调用AQS的acquire()方法获取锁,传的值为1
acquire(1);
}
попробуйте получить блокировку напрямую,
// AbstractQueuedSynchronizer.acquire()
public final void acquire(int arg) {
// 尝试获取锁
// 如果失败了,就排队
if (!tryAcquire(arg) &&
// 注意addWaiter()这里传入的节点模式为独占模式
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
Конкретный процесс получения блокировки
-
getState()получить статус синхронизацииstateзначение, чтобы судить, равно ли оно 0:- Если значение переменной состояния равно 0, это означает, что ни у кого еще нет блокировки. Использование hasQueuedPredecessors() гарантирует, что и новый поток, и поток в очереди используют блокировку последовательно. Если в очереди нет другого потока, то текущий поток пытается обновить состояние. Значение равно 1, и оно устанавливается в переменную excelOwnerThread, чтобы подготовиться к последующему повторному получению блокировки..
- Если текущий поток в excelOwnerThread указывает, что он владеет блокировкой, и теперь пытается получить блокировку, значение переменной состояния необходимо изменить.
state+1

// ReentrantLock.FairSync.tryAcquire()
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 状态变量的值为0,说明暂时还没有线程占有锁
if (c == 0) {
// hasQueuedPredecessors()保证了不论是新的线程还是已经排队的线程都顺序使用锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 当前线程获取了锁,并将本线程设置到exclusiveOwnerThread变量中,
//供后续自己可重入获取锁作准备
setExclusiveOwnerThread(current);
return true;
}
}
// 之所以说是重入锁,就是因为在获取锁失败的情况下,还会再次判断是否当前线程已经持有锁了
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 设置到state中
// 因为当前线程占有着锁,其它线程只会CAS把state从0更新成1,是不会成功的
// 所以不存在竞争,自然不需要使用CAS来更新
setState(nextc);
return true;
}
return false;
}
Если приобретение не удается и ставится в очередь, как с этим бороться? С помощью метода spin потоки в очереди продолжают пытаться получить операцию блокировки, и середина может быть прервана путем прерывания,
-
Если предыдущий узел текущего узла является головным узлом, это означает, что ваша очередь получить блокировку, вызовите
tryAcquire()метод пытается снова получить блокировкуfinal boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; // 自旋 for (;;) { // 当前节点的前一个节点, final Node p = node.predecessor(); // 如果当前节点的前一个节点为head节点,则说明轮到自己获取锁了 // 调用ReentrantLock.FairSync.tryAcquire()方法再次尝试获取锁 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; // 等待状态为SIGNAL(等待唤醒),直接返回true if (ws == Node.SIGNAL) return true; // 前一个节点的状态大于0,已取消状态 if (ws > 0) { // 把前面所有取消状态的节点都从链表中删除 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 前一个Node的状态小于等于0,则把其状态设置为等待唤醒 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
Прочитав процесс получения блокировок, знаете ли вы, как ReentrantLock обеспечивает честные блокировки? по фактуtryAcquire()в реализации.
Как ReentrantLock реализует справедливую блокировку?
существуетtryAcquire()используется при реализацииhasQueuedPredecessors()Это гарантирует, что поток использует блокировку FIFO «первым поступил — первым обслужен» и не вызывает проблем «голодания».
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 状态变量的值为0,说明暂时还没有线程占有锁
if (c == 0) {
// hasQueuedPredecessors()保证了不论是新的线程还是已经排队的线程都顺序使用锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
....
}
...
}
}
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
tryAcquire проверит, есть ли еще элемент-предшественник в очереди CLH, и, если он все еще есть, продолжит ожидание, таким образом, чтобы обеспечить принцип «первым пришел, первым обслужен».
Так как же ReentrantLock обеспечивает повторный вход? Как это повторно?
Как ReentrantLock обеспечивает повторный вход?
На самом деле, очень просто, после приобретения блокировки, установите переменную идентификации для текущего потокаexclusiveOwnerThread, когда нить снова входит в судexclusiveOwnerThreadОценивается, равна ли переменная этому потоку.
protected final boolean tryAcquire(int acquires) {
// 状态变量的值为0,说明暂时还没有线程占有锁
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 当前线程获取了锁,并将本线程设置到exclusiveOwnerThread变量中,
//供后续自己可重入获取锁作准备
setExclusiveOwnerThread(current);
return true;
}
} //之所以说是重入锁,就是因为在获取锁失败的情况下,还会再次判断是否当前线程已经持有锁了
else if (current == getExclusiveOwnerThread()) {
...
}
}
Прочитав процесс приобретения замков с честными замками, мы также знаем о получении замков с нечестными замками, поэтому давайте посмотрим.
В чем разница между режимом честной блокировки ReentrantLock и блокировкой получения недобросовестной блокировки?
На самом деле разница между недобросовестным получением блокировки и получением блокировки в основном заключается в следующем:
-
передается в функцию сборки
falseили null для создания несправедливой блокировкиNonfairSync,trueсоздавать честные замки, -
Несправедливые блокировки проверяйте в первую очередь при получении замков
stateсостояние, а затем выполнять непосредственноaqcuire(1), что может повысить эффективность,final void lock() { if (compareAndSetState(0, 1)) //修改同步状态的值成功的话,设置当前线程为独占的线程 setExclusiveOwnerThread(Thread.currentThread()); else //获取锁 acquire(1); } -
существует
tryAcquire()Нет вhasQueuedPredecessors()Гарантируется, что как новые потоки, так и уже поставленные в очередь потоки используют блокировки по порядку.
Остальные функции аналогичны. Поняв получение блокировок, мы сможем лучше понять освобождение блокировок ReentrantLock::unlock(), что также относительно просто.
ReentrantLock::unlock() снимает блокировку, как разбудить потоки в очереди ожидания?
-
Снять блокировку, занятую текущим потоком
protected final boolean tryRelease(int releases) { // 计算释放后state值 int c = getState() - releases; // 如果不是当前线程占用锁,那么抛出异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { // 锁被重入次数为0,表示释放成功 free = true; // 清空独占线程 setExclusiveOwnerThread(null); } // 更新state值 setState(c); return free; } -
Если выпуск прошел успешно, вам нужно разбудить потоки в очереди ожидания, сначала проверьте, является ли состояние головного узла SIGNAL, если это так, разбудите поток, связанный со следующим узлом головного узла, если выпуск не удался, верните false, чтобы указать, что разблокировка не удалась.
- Установите состояние ожидания в 0,
- Когда следующий узел головного узла не пуст, он разбудит узел напрямую.Если узел пуст, хвост очереди начнет перемещаться вперед, найдет последний непустой узел, а затем проснется. .
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;//这里的s是头节点(现在是头节点持有锁)的下一个节点,也就是期望唤醒的节点
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); //唤醒s代表的线程
}
Помимо реентерабельности вышеупомянутого ReentrantLock, какие еще характеристики он может иметь помимо характеристик честных/несправедливых блокировок?
Какие еще функции есть у ReentrantLock помимо реентерабельности?
- Поддержка прерывания потока, просто добавьте флаг прерывания в поток
interrupted, это никак не повлияет на работающий поток.Что нужно сделать в соответствии с этим флагом прерывания, решать пользователю. Например, когда реализовано ожидание блокировки, блокировка не будет получена в течение 5 секунд, ожидание прерывается, а поток продолжает заниматься другими делами. - механизм тайм-аута, в
ReetrantLock::tryLock(long timeout, TimeUnit unit)Обеспечивает функцию приобретения замков со временем. Его семантика - вернуть true, если замок приобретен в течение указанного времени, и false, если его нельзя приобрести. Этот механизм позволяет избежать потока, ожидая на неопределенно, для выпуска блокировки.
Разница между ReentrantLock и Synchronized
- ReentrantLock поддерживает ожидание прерывания, которое может прервать ожидающий поток.
- ReentrantLock для честных блокировок
- ReentrantLock может реализовать выборочное уведомление, то есть может быть несколько очередей условий.
Сценарии использования ReentrantLock
- Сценарий 1: если вы работаете, вы не будете повторять блокировку, в основном для не важных задач от повторного выполнения, таких как очистка полезного временного файла, проверьте наличие определенных ресурсов, операции резервного копирования данных и т. Д.
- Сценарий 2: Если обнаружено, что операция уже выполняется, попробуйте подождать некоторое время, и она не будет выполняться после ожидания тайм-аута, чтобы предотвратить взаимоблокировку, вызванную неправильной обработкой ресурсов в течение длительного времени.
- Сценарий 3. Если обнаружено, что операция заблокирована, дождитесь блокировок одну за другой, в основном из-за конфликтов ресурсов (таких как операции с файлами, синхронная отправка сообщений, операции с отслеживанием состояния и т. д.)
- Сценарий 4: Блокировка может быть прервана, а синхронно выполняемая операция может быть отменена, чтобы предотвратить блокировку, вызванную ненормальной работой в течение длительного времени.
Вы в порядке, офицеры? Если вам это нравится, проведите пальцем, чтобы нажать 💗, нажмите, чтобы подписаться! ! Спасибо за Вашу поддержку!
Добро пожаловать в паблик-аккаунт [Ccww Technology Blog], оригинальные технические статьи будут запущены в ближайшее время