AbstractQueuedSynchronizer详解一分析ReentrantLock源码.docx
《AbstractQueuedSynchronizer详解一分析ReentrantLock源码.docx》由会员分享,可在线阅读,更多相关《AbstractQueuedSynchronizer详解一分析ReentrantLock源码.docx(18页珍藏版)》请在冰豆网上搜索。
AbstractQueuedSynchronizer详解一分析ReentrantLock源码
AbstractQueuedSynchronizer详解
(一)——分析ReentrantLock源码
官方文档如是说
AQS提供了一个框架,一个FIFO的等待队列和一个代表状态的int值。
子类需要定义改变这个状态的protected方法、定义什么状态表示获取到状态以及释放状态。
该类中其中方法提供所有入队和阻塞机制。
子类可以保存其他状态,但是需要使用getState、setState和compareAndSetState方法来原子性地读取更新状态值。
AQS用于一群线程为了得到某个资源,但是同一时刻,只能有部分线程可以使用该资源,对于其他线程,会使用队列将其他线程入队,而一旦有一个线程使用完了资源,那么队列中的某个线程就会获得该资源的使用。
而AQS中未对该资源做出明确说明,只是通过一个int的状态值表示资源的获取与释放。
比如在ReentrantLock中,该资源某一线程持有的锁的个数,当某一线程获得了这把锁,就把状态值置为1,释放则将状态值减1。
那么就会得出状态0表示线程可以使用这把锁,而状态大于0则表示其他线程不可以使用这把锁。
由于ReentrantLock是可以重入的,所以这儿状态值是可以大于1,代表拥有该锁的线程又再一次获得该锁,这些留在下面再讲。
一个状态
/**
*Thesynchronizationstate.
*/
privatevolatileintstate;
/**
*Returnsthecurrentvalueofsynchronizationstate.
*Thisoperationhasmemorysemanticsofa{@codevolatile}read.
*@returncurrentstatevalue
*/
protectedfinalintgetState(){
returnstate;
}
/**
*Setsthevalueofsynchronizationstate.
*Thisoperationhasmemorysemanticsofa{@codevolatile}write.
*@paramnewStatethenewstatevalue
*/
protectedfinalvoidsetState(intnewState){
state=newState;
}
/**
*Atomicallysetssynchronizationstatetothegivenupdated
*valueifthecurrentstatevalueequalstheexpectedvalue.
*Thisoperationhasmemorysemanticsofa{@codevolatile}read
*andwrite.
*
*@paramexpecttheexpectedvalue
*@paramupdatethenewvalue
*@return{@codetrue}ifsuccessful.Falsereturnindicatesthattheactual
*valuewasnotequaltotheexpectedvalue.
*/
protectedfinalbooleancompareAndSetState(intexpect,intupdate){
//Seebelowforintrinsicsuptosupportthis
returnpareAndSwapInt(this,stateOffset,expect,update);
}
AQS中表示状态的值是一个valotile类型的,实现了内存可见性,一旦更新,那么别的线程就可以立即看到了。
而对于更新,使用了Unsafe类的CAS机制实现原子性。
所以关于state的更新以及读取就不需要子类关注了,只需要知道调用这几个方法即可。
队列
AQS支持两种模式,一种是独占式,另一种是共享式。
当使用独占式时,当一个线程得到了资源,那么其他尝试获取资源的线程将不会成功。
当使用共享式时,当一个线程得到了资源,那么其他尝试获取资源的线程可能(也有可能不)会成功。
不同模式的等待线程使用相同的队列,都在同一个队列中入队。
所以队列的每一个节点需要区分是共享模式还是独占模式。
通常,子类只会支持一种模式,但是ReadWriteLock类使用了两种模式。
只支持一种模式的子类不需要重写另一种模式的方法。
下面是队列中节点的定义:
staticfinalclassNode{
//表示共享模式
staticfinalNodeSHARED=newNode();
//表示独占模式
staticfinalNodeEXCLUSIVE=null;
//表明线程已被取消
staticfinalintCANCELLED=1;
//后继节点需要被唤醒
staticfinalintSIGNAL=-1;
//表明线程在等待某个条件的发生
staticfinalintCONDITION=-2;
//表明下一次acquireShared时需要无条件地传播
staticfinalintPROPAGATE=-3;
//该线程的状态
volatileintwaitStatus;
//前驱节点
volatileNodeprev;
//后继节点
volatileNodenext;
//当前线程
volatileThreadthread;
//下一个等待条件的节点或共享节点
NodenextWaiter;
/**
*返回该节点是否是共享的
*/
finalbooleanisShared(){
returnnextWaiter==SHARED;
}
//返回该节点的前驱节点
finalNodepredecessor()throwsNullPointerException{
Nodep=prev;
if(p==null)
thrownewNullPointerException();
else
returnp;
}
Node(){//UsedtoestablishinitialheadorSHAREDmarker
}
Node(Threadthread,Nodemode){//UsedbyaddWaiter
this.nextWaiter=mode;
this.thread=thread;
}
Node(Threadthread,intwaitStatus){//UsedbyCondition
this.waitStatus=waitStatus;
this.thread=thread;
}
}
从上面可以看到AQS的队列是一个双向链表结构的队列。
而在AQS中也保存了一头一尾两个变量,这样入队和出队都很方便。
/**
*Headofthewaitqueue,lazilyinitialized.Exceptfor
*initialization,itismodifiedonlyviamethodsetHead.Note:
*Ifheadexists,itswaitStatusisguaranteednottobe
*CANCELLED.
*/
privatetransientvolatileNodehead;
/**
*Tailofthewaitqueue,lazilyinitialized.Modifiedonlyvia
*methodenqtoaddnewwaitnode.
*/
privatetransientvolatileNodetail;
独占模式的例子
ReentrantLock是可重入的锁,其内部使用的就是独占模式的AQS,那么下面就以ReentrantLock为例,说明一下ReentrantLock的实现原理。
ReentrantLock的使用
ReentrantLock的使用有一定的格式,如下:
classX{
*privatefinalReentrantLocklock=newReentrantLock();
*//...
*
*publicvoidm(){
*lock.lock();//blockuntilconditionholds
*try{
*//...methodbody
*}finally{
*lock.unlock()
*}
*}
*}
lock方法用于尝试获取锁,一旦得到锁后就可以进行操作了,否则线程阻塞;最后处理完事情后调用unlock释放锁。
下面就从这两个方法看起。
AQS的状态在ReentrantLock代表什么?
因为ReentrantLock是可重入的,那么AQS的状态在ReentrantLock代表的是锁的持有数。
虽然一次只能有一个线程持有该锁,但是由于可重入的原因,导致持有线程可以获得多把锁。
lock方法
首先看lock方法,代码如下:
publicvoidlock(){
sync.lock();
}
可以看到lock方法调用sync的lock方法,那么这个sync是个什么呢?
Sync是ReentrantLock的一个内部类,并且在ReentrantLock初始化时创建。
下面是Sync类的定义:
abstractstaticclassSyncextendsAbstractQueuedSynchronizer{
privatestaticfinallongserialVersionUID=-5179523762034025860L;
abstractvoidlock();
/**
*Performsnon-fairtryLock.tryAcquireisimplementedin
*subclasses,butbothneednonfairtryfortrylockmethod.
*/
finalbooleannonfairTryAcquire(intacquires){
finalThreadcurrent=Thread.currentThread();
intc=getState();
if(c==0){
if(compareAndSetState(0,acquires)){
setExclusiveOwnerThread(current);
returntrue;
}
}
elseif(current==getExclusiveOwnerThread()){
intnextc=c+acquires;
if(nextc<0)//overflow
thrownewError("Maximumlockcountexceeded");
setState(nextc);
returntrue;
}
returnfalse;
}
protectedfinalbooleantryRelease(intreleases){
intc=getState()-releases;
if(Thread.currentThread()!
=getExclusiveOwnerThread())
thrownewIllegalMonitorStateException();
booleanfree=false;
if(c==0){
free=true;
setExclusiveOwnerThread(null);
}
setState(c);
returnfree;
}
protectedfinalbooleanisHeldExclusively(){
//Whilewemustingeneralreadstatebeforeowner,
//wedon'tneedtodosotocheckifcurrentthreadisowner
returngetExclusiveOwnerThread()==Thread.currentThread();
}
finalConditionObjectnewCondition(){
returnnewConditionObject();
}
//Methodsrelayedfromouterclass
finalThreadgetOwner(){
returngetState()==0?
null:
getExclusiveOwnerThread();
}
finalintgetHoldCount(){
returnisHeldExclusively()?
getState():
0;
}
finalbooleanisLocked(){
returngetState()!
=0;
}
/**
*Reconstitutestheinstancefromastream(thatis,deserializesit).
*/
privatevoidreadObject(java.io.ObjectInputStreams)
throwsjava.io.IOException,ClassNotFoundException{
s.defaultReadObject();
setState(0);//resettounlockedstate
}
}
可以看到Sync是一个抽象类,其抽象方法是lock方法。
该类有两个实现类,一个是NonfairSync,一个是FairSync。
这两者有什么区别呢?
这两者的区别在于获取不到锁时的表现,NonfairSync是抢占式的,虽然是后来的,但还是首先尝试一把,万一就获得了锁呢?
FairSync则是个规规矩矩的好学生,知道这会有线程占有了锁,那么就老老实实地入队了。
这些在下面的代码会分析到。
知道了有两种不同的Sync之后,那么ReentrantLock是如何指定使用哪一种Sync类的呢?
答案在构造器中,如下:
publicReentrantLock(){
sync=newNonfairSync();
}
publicReentrantLock(booleanfair){
sync=fair?
newFairSync():
newNonfairSync();
}
从代码中可以看到,默认实现是NonfairSync。
那么下面我们就先看NonfairSync的lock具体实现:
finalvoidlock(){
if(compareAndSetState(0,1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire
(1);
}
从上面代码可以看到,首先是尝试将状态从0置为1,即从无锁变有锁,如果成功了,那么说明该线程就得到了这把锁;如果失败了,就会调用acquire
(1)获取一把锁。
其中acquire方法是父类AQS中的方法,如下:
publicfinalvoidacquire(intarg){
if(!
tryAcquire(arg)&&
acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
selfInterrupt();
}
acquire方法以独享模式获取资源,忽略Interrupt。
其中tryAcquire方法为尝试获取资源,如果不能获取,那么调用acquireQueued方法将该线程放入队列中。
首先看tryAcquire方法,该方法在AQS是个空实现,每个实现独占模式的类需要自己实现。
下面看NonFairSync的tryAcquire实现:
protectedfinalbooleantryAcquire(intacquires){
returnnonfairTryAcquire(acquires);
}
可以看到NonFairSync调用的是父类的nonfairTryAcquire方法,下面是该方法的实现:
finalbooleannonfairTryAcquire(intacquires){
//得到本线程
finalThreadcurrent=Thread.currentThread();
//得到状态
intc=getState();
//没有线程持有锁
if(c==0){
//可以将状态从0更新为1,说明本线程成功获取到了锁,返回true
if(compareAndSetState(0,acquires)){
setExclusiveOwnerThread(current);
returntrue;
}
}
//如果本线程和目前持有锁的线程相同,更新持有锁的数量,返回true
elseif(current==getExclusiveOwnerThread()){
intnextc=c+acquires;
if(nextc<0)//overflow
thrownewError("Maximumlockcountexceeded");
setState(nextc);
returntrue;
}
//其余情况,返回false
returnfalse;
}
从上面代码可以看到有几种情况:
-目前没有线程持有锁,并且该线程成功更新状态,返回true
-如果本线程和当前持有锁的线程相同,更新状态,返回true
-其余情况,目前有其他线程获得了锁,返回false
在acquire方法中,如果该线程成功获得了锁,那么返回false,由于短路,不会执行后面的方法了;但如果获取锁失败,那么就会执行acquiredQueued方法,下面首先看其第一个参数addWaiter方法,addWaiter方法根据传入的模式创建节点并将其入队。
//在独占模式下,mode值为null
privateNodeaddWaiter(Nodemode){
//根据线程以及模式创建节点
Nodenode=newNode(Thread.currentThread(),mode);
//得到队列尾端
Nodepred=tail;
//如果队列不为空
if(pred!
=null){
node.prev=pred;
//更新队列尾端
if(compareAndSetTail(pred,node)){
pred.next=node;
returnnode;
}
}
//队列为空的情况或不能更新队列尾端(说明有别的线程入队成功了)
enq(node);
returnnode;
}
从上面代码可以看到,在队列为空的情况或者compareAndSetTail失败的情况下,调用enq方法将节点插入队列中并返回插入节点的前驱节点,下面是enq方法:
privateNodeenq(finalNodenode){
//死循环
for(;;){
Nodet=tail;
//队列为空,新建队头对尾节点
if(t==null){//Mustinitialize
if(compareAndSetHead(newNode()))
tail=head;
}
//一直尝试将节点加入队尾,直至成功
else{
node.prev=t;
if(compareAndSetTail(t,node)){
t.next=node;
returnt;
}
}
}
}
enq方法实现了加入节点的线程的自旋,直至加入队列为止。
在enq方法中有一个奇怪的地方,是在队列为空时,没有以当前节点作为队列头节点,而是创建了一个新节点作为头节点,结构如下图:
可以看到加入的那个结点就是黄色的Head节点,该节点是个空节点,然后才是将后面的节点加入队列。
一旦将节点加入了队列中,接下来还需要做的事情就是挂起线程,下面再看acquireQueued方法,
finalbooleanacquireQueued(finalNodenode,intarg){
booleanfailed=true;
try{
booleaninterrupted=false;
//死循环
for(;;){
finalNodep=node.predecessor();
//如果该节点的前驱节点是头节点(空节点)并且成功获取到了资源,返回
if(p==head&&tryAcquire(arg)){
setHead(node);
p.next=null;//helpGC
failed=false;
returninterrupted;
}
//需要根据前驱节点查看是否需要挂起自己
if(shouldParkAfterFailedAcquire(p,node)&&
parkAndCheckInterrupt())
interrupted=true;
}
}finally{
if(failed)//异常,取消获取
cancelAcquire(node);
}
}
从上面可以看到,也是进入一个死循环,然后如果该节点的前驱节点是头节点并且成功获取到了资源,那么将其出队并返回。
否则调用shouldParkAfterFailedAcquire方法判断该线程是否需要阻塞,如果需要阻塞,还会再调用parkAndCheckInterrupt()方法阻塞线程以及判断线程是否被Inerrupt了。
先看shouldParkAfterFa