Java源码解析之可重入锁ReentrantLock
本文基于jdk1.8进行分析。
ReentrantLock是一个可重入锁,在ConcurrentHashMap中使用了ReentrantLock。
首先看一下源码中对ReentrantLock的介绍。如下图。ReentrantLock是一个可重入的排他锁,它和synchronized的方法和代码有着相同的行为和语义,但有更多的功能。ReentrantLock是被最后一个成功lock锁并且还没有unlock的线程拥有着。如果锁没有被别的线程拥有,那么一个线程调用lock方法,就会成功获取锁并返回。如果当前线程已经拥有该锁,那么lock方法会立刻返回。这个可以通过isHeldByCurrentThread方法和getHoldCount方法进行验证。除了这部分介绍外,类前面的javadoc文档很长,就不在这里全部展开。随着后面介绍源码,会一一涉及到。
/** *Areentrantmutualexclusion{@linkLock}withthesamebasic *behaviorandsemanticsastheimplicitmonitorlockaccessedusing *{@codesynchronized}methodsandstatements,butwithextended *capabilities. *A{@codeReentrantLock}isownedbythethreadlast *successfullylocking,butnotyetunlockingit.Athreadinvoking *{@codelock}willreturn,successfullyacquiringthelock,when *thelockisnotownedbyanotherthread.Themethodwillreturn *immediatelyifthecurrentthreadalreadyownsthelock.Thiscan *becheckedusingmethods{@link#isHeldByCurrentThread},and{@link *#getHoldCount}.
首先看一下成员变量,如下图。ReentrantLock只有一个成员变量sync,即同步器,这个同步器提供所有的机制。Sync是AbstractQueuedSynchronizer的子类,同时,Sync有2个子类,NonfairSync和FairSync,分别是非公平锁和公平锁。Sync,NonfaireSync和FairSync的具体实现后面再讲。
/**Synchronizerprovidingallimplementationmechanics**/ privatefinalSyncsync;
下面看一下构造函数。如下图。可以看到,ReentrantLock默认是非公平锁,它可以通过参数,指定初始化为公平锁或非公平锁。
/** *Createsaninstanceof{@codeReentrantLock}. *Thisisequivalenttousing{@codeReentrantLock(false)}. **/ publicReentrantLock(){ sync=newNonfairSync(); } /** *Createsaninstanceof{@codeReentrantLock}withthe *givenfairnesspolicy. *@paramfair{@codetrue}ifthislockshoulduseafairorderingpolicy **/ publicReentrantLock(booleanfair){ sync=fair?newFairSync():newNonfairSync(); }
下面看一下ReentrantLock的主要方法。首先是lock方法。如下图。lock方法的实现很简单,就是调用Sync的lock方法。而Sync的lock方法是个抽象的,具体实现在NonfairSync和FairSync中。这里我们先不展开讲,而是先读一下lock方法的注释,看看它的作用。lock方法的作用是获取该锁。分为3种情况。
1,如果锁没有被别的线程占有,那么当前线程就可以获取到锁并立刻返回,并把锁计数设置为1。
2,如果当前线程已经占有该锁了,那么就会把锁计数加1,立刻返回。
3,如果锁被另一个线程占有了,那么当前线程就无法再被线程调度,并且开始睡眠,直到获取到锁,在获取到到锁时,会把锁计数设置为1。
lockInterruptibly方法与lock功能类似,但lockInterruptibly方法在等待的过程中,可以响应中断。
/** *Acquiresthelock. *Acquiresthelockifitisnotheldbyanotherthreadandreturns *immediately,settingthelockholdcounttoone. *
Ifthecurrentthreadalreadyholdsthelockthenthehold *countisincrementedbyoneandthemethodreturnsimmediately. *
Ifthelockisheldbyanotherthreadthenthe *currentthreadbecomesdisabledforthreadscheduling *purposesandliesdormantuntilthelockhasbeenacquired, *atwhichtimethelockholdcountissettoone. **/ publicvoidlock(){ sync.lock(); } publicvoidlockInterruptibly()throwsInterruptedException{ sync.acquireInterruptibly(1); }
下面,详细看一下非公平锁和公平锁中对lock函数的实现。如下图。下图同时列出了公平锁和非公平锁中lock的实现逻辑。从注释和代码逻辑中,都可以看出,非公平锁进行lock时,先尝试立刻闯入(抢占),如果成功,则获取到锁,如果失败,再执行通常的获取锁的行为,即acquire(1)。
/** *非公平锁中的lock *Performslock.Tryimmediatebarge,backinguptonormal *acquireonfailure. **/ finalvoidlock(){ if(compareAndSetState(0,1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } //公平锁中的lock finalvoidlock(){ acquire(1); }
那么,我们首先了解下,非公平锁“尝试立刻闯入”,究竟做了什么。稍后再继续讲解通常的获取锁的行为。下图是立即闯入行为compareAndSetState(0,1)的实现。从compareAndSetState函数的注释中,可以知道,如果同步状态值与期望值相等,那么就把它的值设置为updated值。否则同步状态值与期望值不相等,则返回false。这个操作和volatile有着相同的内存语义,也就是说,这个操作对其他线程是可见的。compareAndSetState函数注释里描述的功能,是通过unsafe.compareAndSwapInt方法实现的,而unsafe.compareAndSwapInt是一个native方法,是用c++实现的。那么继续追问,c++底层是怎么实现的?C++底层是通过CAS指令来实现的。什么是CAS指令呢?来自维基百科的解释是,CAS,比较和交换,CompareandSwap,是用用于实现多线程原子同步的指令。它将内存位置的内容和给定值比较,只有在相同的情况下,将该内存的值设置为新的给定值。这个操作是原子操作。那么继续追问,CAS指令的原子性,是如何实现的呢?我们都知道指令时CPU来执行的,在多CPU系统中,内存是共享的,内存和多个cpu都挂在总线上,当一个CPU执行CAS指令时,它会先将总线LOCK位点设置为高电平。如果别的CPU也要执行CAS执行,它会发现总线LOCK位点已经是高电平了,则无法执行CAS执行。CPU通过LOCK保证了指令的原子执行。
现在来看一下非公平锁的lock行为,compareAndSetState(0,1),它期望锁状态为0,即没有别的线程占用,并把新状态设置为1,即标记为占用状态。如果成功,则非公平锁成功抢到锁,之后setExclusiveOwnerThread,把自己设置为排他线程。非公平锁这小子太坏了。如果抢占失败,则执行与公平锁相同的操作。
/** *Atomicallysetssynchronizationstatetothegivenupdated *valueifthecurrentstatevalueequalstheexpectedvalue. *Thisoperationhasmemorysemanticsofa{@codevolatile}read *andwrite. *@paramexpecttheexpectedvalue *@paramupdatethenewvalue *@return{@codetrue}ifsuccessful.Falsereturnindicatesthattheactual *valuewasnotequaltotheexpectedvalue. **/ protectedfinalbooleancompareAndSetState(intexpect,intupdate){ //Seebelowforintrinsicssetuptosupportthis returnunsafe.compareAndSwapInt(this,stateOffset,expect,update); } publicfinalnativebooleancompareAndSwapInt(Objectvar1,longvar2,intvar4,intvar5);
下面看一下公平锁获取锁时的行为。如下图。这部分的逻辑有些多,请阅读代码中的注释进行理解。
/** *公平锁的lock **/ finalvoidlock(){ acquire(1); } /** *Acquiresinexclusivemode,ignoringinterrupts.Implemented *byinvokingatleastonce{@link#tryAcquire}, *returningonsuccess.Otherwisethethreadisqueued,possibly *repeatedlyblockingandunblocking,invoking{@link *#tryAcquire}untilsuccess.Thismethodcanbeused *toimplementmethod{@linkLock#lock}. *@paramargtheacquireargument.Thisvalueisconveyedto *{@link#tryAcquire}butisotherwiseuninterpretedand *canrepresentanythingyoulike. **/ publicfinalvoidacquire(intarg){ /** *acquire首先进行tryAcquire()操作。如果tryAcquire()成功时则获取到锁,即刻返回。 *如果tryAcquire()false时,会执行acquireQueued(addWaiter(Node.EXCLUSIVE),arg) *操作。如果acquireQueued(addWaiter(Node.EXCLUSIVE),arg)true时,则当前线程中断自己。 *如果acquireQueued(addWaiter(Node.EXCLUSIVE),arg)false,则返回。 *其中tryAcquire()操作在NonfairSync中和FairSync中实现又有所区别。 **/ if(!tryAcquire(arg)&& acquireQueued(addWaiter(Node.EXCLUSIVE),arg)) selfInterrupt(); } /** *NonfairSync中的tryAcquire。 *@paramacquires *@return **/ protectedfinalbooleantryAcquire(intacquires){ returnnonfairTryAcquire(acquires); } /** *Performsnon-fairtryLock.tryAcquireisimplementedin *subclasses,butbothneednonfairtryfortrylockmethod. **/ finalbooleannonfairTryAcquire(intacquires){ finalThreadcurrent=Thread.currentThread(); //首先获取当前同步状态值 intc=getState(); if(c==0){ //c为0,表示目前没有线程占用锁。没有线程占用锁时,当前线程尝试抢锁,如果抢锁成功,则返回true。 if(compareAndSetState(0,acquires)){ setExclusiveOwnerThread(current); returntrue; } } elseif(current==getExclusiveOwnerThread()){ //c不等于0时表示锁被线程占用。如果是当前线程占用了,则将锁计数加上acquires,并返回true。 intnextc=c+acquires; if(nextc<0)//overflow thrownewError("Maximumlockcountexceeded"); setState(nextc); returntrue; } //以上情况都不是时,返回false,表示非公平抢锁失败。 returnfalse; } /** *FairversionoftryAcquire.Don'tgrantaccessunless *recursivecallornowaitersorisfirst. *这个是公平版本的tryAcquire **/ protectedfinalbooleantryAcquire(intacquires){ finalThreadcurrent=Thread.currentThread(); intc=getState(); if(c==0){ //c=0时表示锁未被占用。这里是先判断队列中前面是否有别的线程。没有别的线程时,才进行CAS操作。 //公平锁之所以公平,正是因为这里。它发现锁未被占用时,首先判断等待队列中是否有别的线程已经在等待了。 //而非公平锁,发现锁未被占用时,根本不管队列中的排队情况,上来就抢。 if(!hasQueuedPredecessors()&& compareAndSetState(0,acquires)){ setExclusiveOwnerThread(current); returntrue; } } elseif(current==getExclusiveOwnerThread()){ intnextc=c+acquires; if(nextc<0) thrownewError("Maximumlockcountexceeded"); setState(nextc); returntrue; } returnfalse; } /** *Acquiresinexclusiveuninterruptiblemodeforthreadalreadyin *queue.Usedbyconditionwaitmethodsaswellasacquire. *当抢锁失败时,先执行addWaiter(Node.EXCLUSIVE),将当前线程加入等待队列,再执行该方法。 *该方法的作用是中断当前线程,并进行检查,知道当前线程是队列中的第一个线程,并且抢锁成功时, *该方法返回。 *@paramnodethenode *@paramargtheacquireargument *@return{@codetrue}ifinterruptedwhilewaiting **/ 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); } }
接下来是tryLock方法。代码如下。从注释中我们可以理解到,只有当调用tryLock时锁没有被别的线程占用,tryLock才会获取锁。如果锁没有被另一个线程占用,那么就获取锁,并立刻返回true,并把锁计数设置为1.甚至在锁被设置为公平排序的情况下,若果锁可用,调用tryLock会立刻获取锁,而不管有没有别的线程在等待锁了。从这里我们总结出,不管可重入锁是公平锁还是非公平锁,tryLock方法只会是非公平的。
/** *Acquiresthelockonlyifitisnotheldbyanotherthreadatthetime *ofinvocation. *Acquiresthelockifitisnotheldbyanotherthreadand *returnsimmediatelywiththevalue{@codetrue},settingthe *lockholdcounttoone.Evenwhenthislockhasbeensettousea *fairorderingpolicy,acallto{@codetryLock()}will *immediatelyacquirethelockifitisavailable,whetherornot *otherthreadsarecurrentlywaitingforthelock. *This"barging"behaviorcanbeusefulincertain *circumstances,eventhoughitbreaksfairness.Ifyouwanttohonor *thefairnesssettingforthislock,thenuse *{@link#tryLock(long,TimeUnit)tryLock(0,TimeUnit.SECONDS)} *whichisalmostequivalent(italsodetectsinterruption). *
Ifthecurrentthreadalreadyholdsthislockthenthehold *countisincrementedbyoneandthemethodreturns{@codetrue}. *
Ifthelockisheldbyanotherthreadthenthismethodwillreturn *immediatelywiththevalue{@codefalse}. *@return{@codetrue}ifthelockwasfreeandwasacquiredbythe *currentthread,orthelockwasalreadyheldbythecurrent *thread;and{@codefalse}otherwise **/ publicbooleantryLock(){ returnsync.nonfairTryAcquire(1); } publicbooleantryLock(longtimeout,TimeUnitunit) throwsInterruptedException{ returnsync.tryAcquireNanos(1,unit.toNanos(timeout)); }
接下来是释放锁的方法unlock。代码如下。unlock方式的实现,是以参数1来调用sync.release方法。而release方法是如何实现的呢?release方法首先会调用tryRelease方法,如果tryRelease成功,则唤醒后继者线程。而tryRelease的实现过程十分清晰,首先获取锁状态,锁状态减去参数(放锁次数),得到新状态。然后判断持有锁的线程是否为当前线程,如果不是当前线程,则抛出IllegalMonitorStateException。然后判断,如果新状态为0,说明放锁成功,则把持有锁的线程设置为null,并返回true。如果新状态不为0,则返回false。从tryRelease的返回值来看,它返回的true或false,指的是否成功的释放了该锁。成功的释放该锁的意思是彻底释放锁,别的线程就可以获取锁了。这里要认识到,即便tryRelease返回false,它也只是说明了锁没有完全释放,本次调用的这个释放次数值,依然是释放成功的。
/** *Attemptstoreleasethislock. *Ifthecurrentthreadistheholderofthislockthenthehold *countisdecremented.Iftheholdcountisnowzerothenthelock *isreleased.Ifthecurrentthreadisnottheholderofthis *lockthen{@linkIllegalMonitorStateException}isthrown. *@throwsIllegalMonitorStateExceptionifthecurrentthreaddoesnot *holdthislock **/ publicvoidunlock(){ sync.release(1); } /** *Releasesinexclusivemode.Implementedbyunblockingoneor *morethreadsif{@link#tryRelease}returnstrue. *Thismethodcanbeusedtoimplementmethod{@linkLock#unlock}. *@paramargthereleaseargument.Thisvalueisconveyedto *{@link#tryRelease}butisotherwiseuninterpretedand *canrepresentanythingyoulike. *@returnthevaluereturnedfrom{@link#tryRelease} **/ publicfinalbooleanrelease(intarg){ if(tryRelease(arg)){ Nodeh=head; if(h!=null&&h.waitStatus!=0) unparkSuccessor(h); returntrue; } returnfalse; } protectedfinalbooleantryRelease(intreleases){ intc=getState()-releases; if(Thread.currentThread()!=getExclusiveOwnerThread()) thrownewIllegalMonitorStateException(); booleanfree=false; if(c==0){ free=true; setExclusiveOwnerThread(null); } setState(c); returnfree; } /** *Wakesupnode'ssuccessor,ifoneexists. *@paramnodethenode **/ privatevoidunparkSuccessor(Nodenode){ /** *Ifstatusisnegative(i.e.,possiblyneedingsignal)try *toclearinanticipationofsignalling.ItisOKifthis *failsorifstatusischangedbywaitingthread. **/ intws=node.waitStatus; if(ws<0) compareAndSetWaitStatus(node,ws,0); /** *Threadtounparkisheldinsuccessor,whichisnormally *justthenextnode.Butifcancelledorapparentlynull, *traversebackwardsfromtailtofindtheactual *non-cancelledsuccessor. **/ Nodes=node.next; if(s==null||s.waitStatus>0){ s=null; for(Nodet=tail;t!=null&&t!=node;t=t.prev) if(t.waitStatus<=0) s=t; } if(s!=null) LockSupport.unpark(s.thread); }
接下来是newCondition方法。关于Condition这里不展开介绍,只是了解下该方法的作用。如下图。该方法返回一个和这个锁实例一起使用的Condition实例。返回的Condition实例支持和Object的监控方法例如wait-notify和notifyAll相同的用法。
- 1,如果没有获取锁,调用Condition的await,signal,signalAll方法的任何一个时,会抛出IllegalMonitorStateException异常。
- 2,调用Condition的await方法时,锁也会释放,在await返回之前,锁会被重新获取,并且锁计数会恢复到调用await方法时的值。
- 3,如果一个线程在等待的过程中被中断了,那么等待就会结束,并抛出InterruptedException异常,线程的中断标志位会被清理。
- 4,等待的线程以FIFO的顺序被唤醒。
- 5,从await方法返回的线程们的获取到锁的顺序,和线程最开始获取锁的顺序相同,这是未指定情况下的默认实现。但是,公平锁更钟爱那些已经等待了最长时间的线程。
/** *Returnsa{@linkCondition}instanceforusewiththis *{@linkLock}instance. *Thereturned{@linkCondition}instancesupportsthesame *usagesasdothe{@linkObject}monitormethods({@link *Object#wait()wait},{@linkObject#notifynotify},and{@link *Object#notifyAllnotifyAll})whenusedwiththebuilt-in *monitorlock. *
-
*
- Ifthislockisnotheldwhenanyofthe{@linkCondition} *{@linkplainCondition#await()waiting}or{@linkplain *Condition#signalsignalling}methodsarecalled,thenan{@link *IllegalMonitorStateException}isthrown. *
- Whenthecondition{@linkplainCondition#await()waiting} *methodsarecalledthelockisreleasedand,beforethey *return,thelockisreacquiredandthelockholdcountrestored *towhatitwaswhenthemethodwascalled. *
- Ifathreadis{@linkplainThread#interruptinterrupted} *whilewaitingthenthewaitwillterminate,an{@link *InterruptedException}willbethrown,andthethread's *interruptedstatuswillbecleared. *
- WaitingthreadsaresignalledinFIFOorder. *
- Theorderingoflockreacquisitionforthreadsreturning *fromwaitingmethodsisthesameasforthreadsinitially *acquiringthelock,whichisinthedefaultcasenotspecified, *butforfairlocksfavorsthosethreadsthathavebeen *waitingthelongest. *
可重入锁还有一些其他的方法,这里就不一一介绍了。Thisistheend.
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。如果你想了解更多相关内容请查看下面相关链接