一文疏通AQS到ReentrantLock
AQS是什么
AQS全称是AbstractQueuedSynchronizer,是juc下一个中心的抽象类,用于构建各种同步器和锁
比如咱们熟悉的ReentrantLock、ReadWriteLock、CountDownLatch等等是根据AQS
作业原理
下面这张图很清晰了反映了AQS的作业原理
当线程获取锁时,即企图对state变量做修正,如修正成功则获取锁;
如修正失败则包装为节点挂载到行列中,等候持有锁的线程开释锁并唤醒行列中的节点
首要在AQS里面,有几个中心的组成
●state:同享资源的状况
●以Node节点组成的双端行列——CLH
●两个保护行列的Node节点head和tail
AQS根本的属性(源码)
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
//头节点
private transient volatile Nodehead;
//尾节点
private transient volatile Nodetail;
//同步状况
private volatile intstate;
static final class Node {
//节点状况
volatile intwaitStatus;
//前驱节点
volatile Nodeprev;
//后继节点
volatile Nodenext;
//当时节点所代表的线程
volatile Threadthread;
//等候行列运用时的后继节点指针
NodenextWaiter;
}
}
这儿的Node的waitStatus需求留心一下:
该字段共有5种取值:
●CANCELLED=1。节点引用线程因为等候超时或被打断时的状况。
●SIGNAL=-1。后继节点线程需求被唤醒时的当时节点状况。当行列中参加后继节点被挂起(block)时,其前驱节点会被设置为SIGNAL状况,表明该节点需求被唤醒。
●CONDITION=-2。当节点线程进入condition行列时的状况。(见ConditionObject)
●PROPAGATE=-3。仅在开释同享锁releaseShared时仇人节点运用。(见同享锁分析)
●0。节点初始化时的状况
独占锁分析
acquire(int)
public final void acquire(intarg) {
//tryAcquire需完结类处理
//如获取资源成功,直接回来
if (!tryAcquire(arg) &&
//如获取资源失败,将线程包装为Node增加到行列中堵塞等候
acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
//如堵塞线程被打断
selfInterrupt();
}
acquire中心为tryAcquire、addWaiter和acquireQueued三个函数,其间tryAcquire需详细子类完结。每逢线程调用acquire时都首要会调用tryAcquire,失败后才会挂载到行列,因此acquire完结默以为非公正锁(后到的线程可能会经过这儿的tryAcquire直接拿到锁)
addWaiter将线程包装为节点node,尾插式参加到行列中,如行列为空,则会增加一个空的头节点。值得注意的是addWaiter中的enq办法,经过CAS+自旋的办法处理尾节点增加冲突。
//sync行列中的结点在独占且疏忽中止的形式下获取(资源)
final boolean acquireQueued(final Nodenode, intarg) {
//标志
booleanfailed= true;
try {
//中止标志
booleaninterrupted= false;
for (;;) { //无限循环
//获取node节点的前驱结点
final Nodep=node.predecessor();
if (p==head&& tryAcquire(arg)) { //前驱为头节点而且成功获得锁
setHead(node); //设置头节点
p.next= null; //helpGC
failed= false; //设置标志
returninterrupted;
}
if (shouldParkAfterFailedAcquire(p,node) &&
parkAndCheckInterrupt())//
//shouldParkAfterFailedAcquire只有当该节点的前驱结点的状况为SIGNAL时,才能够对该结点所封装的线程进行park操作。否则,将不能进行park操作。
//parkAndCheckInterrupt首要执行park操作,即禁用当时线程,然后回来该线程是否现已被中止
interrupted= true;
}
} finally {
if (failed)
cancelAcquire(node);//直接把这个节点置空了
}
}
acquireQueue在线程节点参加行列后判别是否可再次测验获取资源,如不能获取则将其前驱节点标志为SIGNAL状况(表明其需求被unpark唤醒)后,则经过park进入堵塞状况
release(int)
public final boolean release(intarg) {
if (tryRelease(arg)) { //开释成功
//保存头节点
Nodeh=head;
if (h!= null &&h.waitStatus!= 0) //头节点不为空而且头节点状况不为0
unparkSuccessor(h); //开释头节点的后继结点
return true;
}
return false;
}
private void unparkSuccessor(Nodenode) {
//这儿,node一般为当时线程地点的结点。
intws=node.waitStatus;
if (ws< 0)//置零当时线程地点的结点状况,答应失败。
compareAndSetWaitStatus(node,ws, 0);
Nodes=node.next;//找到下一个需求唤醒的结点s
if (s== null ||s.waitStatus> 0) {//假如为空或已取消
s= null;
for (Nodet=tail;t!= null &&t!=node;t=t.prev) //从后向前找。
if (t.waitStatus<= 0)//从这儿能够看出,<=0的结点,都是还有效的结点。
s=t;
}
if (s!= null)
LockSupport.unpark(s.thread);//唤醒
}
release流程较为简略,测验开释成功后,即从头结点开端唤醒其后继节点,如后继节点被取消,则转为从尾部开端找堵塞的节点将其唤醒。堵塞节点被唤醒后,即进入acquireQueued中的for(;;)循环开端新一轮的资源竞争
总结
上述内容细心看完,现已能够理解根本的AQS作业原理了,但是咱们的完结类需求完结两个try的办法(因为默许是直接抛异常,这可不可),所以下面的ReentrantLock其实并没有增加很多的锁的细节,根本上用的还是AQS这套锁的流程,无疑便是进行了公正与非公正的分类,以及完结对应的try办法
ReentrantLock的类结构
ReentrantLock是Lock接口的完结类,根本都是经过聚合了一个行列同步器(AQS)的子类完结线程访问控制的
ReentrantLock完结了Lock接口,有三个内部类,其间Sync承继自AQS,然后两者承继自Sync,它们都承继了AQS的才能。
公正锁与非公正锁
结构函数
//生成一个公正锁
static Locklock= new ReentrantLock(true);
//生成一个非公正锁
static Locklock= new ReentrantLock(false);
static Locklock= new ReentrantLock();//默许参数便是false
public ReentrantLock(booleanfair) {
sync=fair? new FairSync() : new NonfairSync();//FairSync表明公正锁,NonfairSync表明非公正锁
}
详细完结tryAcquire
其公正的中心办法:
/*判别等候行列中是否存在等候中的线程*/
public final boolean hasQueuedPredecessors() {
//Thecorrectnessofthisdependsonheadbeinginitialized
//beforetailandonhead.nextbeingaccurateifthecurrent
//threadisfirstinqueue.
Nodet=tail; //Readfieldsinreverseinitializationorder
Nodeh=head;
Nodes;
//判别是否存在先等候的线程详细分析如下
returnh!=t&& ((s=h.next) == null ||s.thread!= Thread.currentThread());
}
公正锁和非公正锁的lock()办法仅有的差异就在于公正锁在获取同步状况时多了一个限制条件:hasQueuedPredecessors()—–公正锁加锁时判别等候行列中是否存在有效节点的办法
tryRelease
protected final boolean tryRelease(intreleases) {
intc= getState() -releases;
//获取独占锁的线程才能够开释线程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
booleanfree= false;
//state==0表明所有锁已开释
if (c== 0) {
free= true;
setExclusiveOwnerThread(null);
}
setState(c);
returnfree;
}
总结
ReentrantLock完结了Lock接口,有三个内部类,其间Sync承继自AQS,然后两者承继自Sync,它们都承继了AQS的才能。
本质上来说ReentrantLock的底层原理便是AQS。
在Sync的两个子类FairSync和NonfairSync分别是公正锁战略和非公正锁战略的完结(公正锁多了一个查看是否有其他等候线程的条件)。然后完结了不同的tryAcquire(intacquires),然后在线程测验获取锁时,执行不同的战略
参阅:
Java 多线程并发 【10】ReentrantLock – 掘金 ()
10分钟从源码级别搞懂AQS(AbstractQueuedSynchronizer) – 掘金 ()
【后端面经-Java】公正锁和加锁流程 – 掘金 ()