AbstractQueuedSynchronizer详解
Posted xxzblog
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AbstractQueuedSynchronizer详解相关的知识,希望对你有一定的参考价值。
原文链接:https://uyiplus.com/2020/aqs-01
AbstractQueuedSynchronizer提供一个框架,用于实现依赖于先进先出(FIFO)等待队列的阻塞锁和相关的同步器(semaphores(信号量),events(事件)等)。
这个类旨在为大多数依赖单个原子int值表示state的同步器提供有用的基础。子类必须定义更改此state的protected方法,并定义该state对于获取或释放此对象而言意味着什么。鉴于这些,此类中的其他方法将执行所有排队和阻塞机制。子类可以维护其他状态字段,但仅跟踪使用getState,setState,compareAndSetState方法进行原子更新的int值的同步性。
子类应该定义为用于实现其封闭类的同步属性的非public内部帮助器类。类AbstractQueuedSynchronizer没有实现任何同步接口。相反,它定义了acquireInterruptible之类的方法,可以通过具体的锁和相关的同步器适当地调用这些方法来实现其public方法。
此类支持默认exclusive(互斥)模式和shared(共享)模式之一或两者。当以exclusive方式进行获取时,其他线程尝试进行的获取将无法成功。由多个线程获取的shared模式可能(但不一定)成功。该类不理解这些差异,只是从机械意义上说,当成功获取shared模式时,下一个等待线程(如果存在)也必须确定它是否也可以获取。在不同模式下等待的线程共享相同的FIFO队列。通常,实现子类仅支持这些模式之一,但在ReadWriteLock中两者(exclusive和shared)都发挥了作用。仅支持exclusive模式或仅支持shared模式的子类无需定义支持未使用模式的方法。
此类定义了一个嵌套的ConditionObject类,该类可以被支持exclusive模式的子类用作Condition实现,为此方法isHeldExclusively报告是否针对当前线程专有地保持同步,使用getState当前值调用的方法release会完全释放此对象,并且给定已保存的状态值,acquire最终会将此对象恢复为先前的获取状态。否则,没有AbstractQueuedSynchronizer方法会创建这样的条件,因此,如果无法满足此约束,请不要使用它。 ConditionObject的行为当然取决于其同步器实现的语义。
此类提供了内部队列的检查,检测和监视方法,以及条件对象的类似方法。可以根据需要使用AbstractQueuedSynchronizer将它们导出到类中以实现其同步机制。
此类的序列化仅存储基础原子整数维护状态,因此反序列化的对象具有空线程队列。需要可序列化的典型子类将定义一个readObject方法,该方法可在反序列化时将其恢复为已知的初始状态。
用法
要将此类用作同步器的基础,请使用getState,setState,compareAndSetState重新定义以下方法:
tryAcquire
tryRelease
tryAcquireShared
tryReleaseShared
isHeldExclusively
默认情况下,这些方法中的每一个都会引发UnsupportedOperationException。这些方法的实现必须在内部是线程安全的,并且通常应简短且不阻塞。定义这些方法是仅仅支持的使用此类的方法。所有其他方法都声明为final,因为它们不能独立变化。
您可能还会发现从AbstractOwnableSynchronizer继承的方法对于跟踪拥有独占同步器的线程很有用。鼓励您使用它们
-这使监视和诊断工具可以帮助用户确定哪些线程持有锁。
即使此类基于内部FIFO队列,它也不会自动执行FIFO获取策略。exclusive同步的核心采取以下形式:
Acquire:
while (!tryAcquire(arg))
// 排队线程(如果尚未排队)
// 可能阻止当前线程
Release:
if (tryRelease(arg))
// 释放阻止第一个排队的线程
// 共享模式相似,但可能涉及级联信号。
因为获取队列中的获取检查是在排队之前被调用的,所以新获取线程可能会在被阻塞和排队的其他线程之前插入。但是,如果需要,您可以定义tryAcquire和/或tryAcquireShared以通过内部调用一种或多种检查方法来禁用插入,从而提供一个fair(公平) FIFO获取顺序。特别是,如果hasQueuedPredecessors(一种专门为公平同步器设计的方法)返回true,则大多数公平同步器都可以定义tryAcquire以返回false。其他变化也是可能的。
吞吐量和可扩展性通常是
默认插入(也称为**greedy(贪心) **,renouncement(放弃)和convoy-avoidance(避免车队))策略。尽管不能保证这是公平的,也可以避免饥饿,但允许在较早排队的线程在较晚排队的线程之前进行重新竞争,并且每个重新争用都可以毫无偏向地成功抵御传入线程。同样,尽管获取不是通常意义上的旋转,但它们可能会在阻塞之前执行tryAcquire的多次调用,并插入其他计算。当仅短暂地保持排他同步时,这将提供旋转的大部分好处,而在不进行排他同步时,则不会带来很多负担。如果需要的话,您可以通过在调用之前对获取方法进行“快速路径”检查来增强此功能,可能会预先检查hasContended和/或hasQueuedThreads以仅在同步器可能不这样做的情况下这样做争辩。
此类为同步提供了有效且可扩展的基础,部分原因是通过将其使用范围专门用于可以依靠int状态,获取和释放参数以及内部FIFO等待队列的同步器。如果不足够,则可以使用java.util.concurrent.atomic atomic类,您自己的自定义java.util.Queue类和LockSupport阻止从较低级别构建同步器支持。
使用范例
这是一个不可重入的互斥锁定类,使用值0表示解锁状态,使用值1表示锁定状态。 尽管不可重入锁并不严格要求记录当前所有者线程,但是无论如何,此类都这样做以使使用情况更易于监视。 它还支持条件并公开一种检测方法:
class Mutex implements Lock, java.io.Serializable
// 我们内部的帮助类
private static class Sync extends AbstractQueuedSynchronizer
// 报告是否处于锁定状态
protected boolean isHeldExclusively()
return getState() == 1;
// 如果状态为零,则获取锁
public boolean tryAcquire(int acquires)
assert acquires == 1; // 否则未使用
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
return true;
return false;
//通过将状态设置为零来释放锁定
protected boolean tryRelease(int releases)
assert releases == 1; // 否则未使用
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
// 提供条件
Condition newCondition() return new ConditionObject();
// 正确反序列化
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException
s.defaultReadObject();
setState(0); // 重置到未锁定状态
// The sync object does all the hard work. We just forward to it.
private final Sync sync = new Sync();
public void lock() sync.acquire(1);
public boolean tryLock() return sync.tryAcquire(1);
public void unlock() sync.release(1);
public Condition newCondition() return sync.newCondition();
public boolean isLocked() return sync.isHeldExclusively();
public boolean hasQueuedThreads() return sync.hasQueuedThreads();
public void lockInterruptibly() throws InterruptedException
sync.acquireInterruptibly(1);
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
这是一个类似于java.util.concurrent.CountDownLatch的闩锁类,只不过它只需要触发一个signal即可。 由于闩锁是非排他性的,因此它使用shared获取和释放方法。
class BooleanLatch
private static class Sync extends AbstractQueuedSynchronizer
boolean isSignalled() return getState() != 0;
protected int tryAcquireShared(int ignore)
return isSignalled() ? 1 : -1;
protected boolean tryReleaseShared(int ignore)
setState(1);
return true;
private final Sync sync = new Sync();
public boolean isSignalled() return sync.isSignalled();
public void signal() sync.releaseShared(1);
public void await() throws InterruptedException
sync.acquireSharedInterruptibly(1);
AbstractQueuedSynchronizer.Node
等待队列节点类。
等待队列是“ CLH”(Craig,Landin和Hagersten)锁定队列的变体。 CLH锁通常用于自旋锁。相反,我们将它们用于阻塞同步器,但是使用相同的基本策略,将有关线程的某些控制信息保存在其节点的前身中。每个节点中的“状态”字段将跟踪线程是否应阻塞。节点的前任释放时会发出信号。否则,队列的每个节点都充当一个特定通知样式的监视器,其中包含一个等待线程。虽然状态字段不控制是否授予线程锁等。线程可能会尝试获取它是否在队列中的第一位。但是先行并不能保证成功。它只赋予了抗辩的权利。因此,当前发布的竞争者线程可能需要重新等待。
要加入CLH锁,您可以自动将其作为新尾部拼接。要出队,您只需设置头字段。
+------+ prev +-----+ +-----+
head | | <---- | | <---- | | tail
+------+ +-----+ +-----+
插入到CLH队列中只需要对“尾巴”执行一次原子操作,因此存在一个简单的原子分界点,即从未排队到排队。同样,出队仅涉及更新“头”。但是,节点需要花费更多的精力来确定其后继者是谁,部分原因是要处理由于超时和中断而可能导致的取消。
“ prev”链接(在原始CLH锁中不使用)主要用于处理取消。如果取消某个节点,则其后继节点(通常)会重新链接到未取消的前任节点。有关自旋锁情况下类似机制的说明,请参见Scott和Scherer的论文,网址为http://www.cs.rochester.edu/u/scott/synchronization/
我们还使用“next”链接来实现阻止机制。每个节点的线程ID保留在其自己的节点中,因此前任通过遍历下一个链接以确定它是哪个线程,从而通知下一个节点唤醒。确定后继者必须避免与新排队的节点竞争以设置其前任节点的“ next”字段。如果需要,可以通过在节点的后继者为空时从原子更新的“tail”向后检查来解决此问题。 (或者换句话说,next链接是一种优化,因此我们通常不需要向后扫描。)
取消将一些保守性引入到基本算法中。由于我们必须轮询其他节点的取消,因此我们可能会遗漏没有注意到已取消的节点在我们前面还是后面。要解决此问题,必须始终在取消时取消后继者,使他们能够稳定在新的前任者身上,除非我们能确定一个未取消的前任者将承担这一责任。
CLH队列需要一个虚拟标头节点才能开始。但是,我们不会在构建过程中创建它们,因为如果没有争执,那将是浪费时间。取而代之的是,构造节点,并在第一次争用时设置头和尾指针。
等待条件的线程使用相同的节点,但使用附加链接。条件只需要在简单(非并行)链接队列中链接节点,因为仅当它们专用时才可以访问它们。等待时,将节点插入条件队列。收到信号后,该节点将转移到主队列。状态字段的特殊值用于标记节点所在的队列。
感谢Dave Dice,Mark Moir,Victor Luchangco,Bill Scherer和Michael Scott以及JSR-166专家组的成员,对此类的设计提出了有益的想法,讨论和批评。
未完待续…
原文链接:https://uyiplus.com/2020/aqs-01
以上是关于AbstractQueuedSynchronizer详解的主要内容,如果未能解决你的问题,请参考以下文章