AbstractQueuedSynchronizer详解一分析ReentrantLock源码.docx

上传人:b****6 文档编号:4387483 上传时间:2022-12-01 格式:DOCX 页数:18 大小:33.83KB
下载 相关 举报
AbstractQueuedSynchronizer详解一分析ReentrantLock源码.docx_第1页
第1页 / 共18页
AbstractQueuedSynchronizer详解一分析ReentrantLock源码.docx_第2页
第2页 / 共18页
AbstractQueuedSynchronizer详解一分析ReentrantLock源码.docx_第3页
第3页 / 共18页
AbstractQueuedSynchronizer详解一分析ReentrantLock源码.docx_第4页
第4页 / 共18页
AbstractQueuedSynchronizer详解一分析ReentrantLock源码.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

AbstractQueuedSynchronizer详解一分析ReentrantLock源码.docx

《AbstractQueuedSynchronizer详解一分析ReentrantLock源码.docx》由会员分享,可在线阅读,更多相关《AbstractQueuedSynchronizer详解一分析ReentrantLock源码.docx(18页珍藏版)》请在冰豆网上搜索。

AbstractQueuedSynchronizer详解一分析ReentrantLock源码.docx

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

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 军事

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1