1、JUC的由来 synchronized关键字是JDK官方人员用C代码写的,在JDK6以前是重量级锁。Java大牛DougLea对synchronized在并发编程条件下的性能表现不满意就自己写了个JUC,以此来提升并发性能,本文要讲的就是JUC并发包下的AbstractQueuedSynchronizer。 在JUC中CountDownLatch、ReentrantLock、ThreadPoolExecutor、ReentrantReadWriteLock等底层用的都是AQS,AQS几乎占据了JUC并发包里的半壁江山,如果想要获取锁可以被中断、超时获取锁、尝试获取锁那就用AQS吧。 DougLea杰作:HashMap、JUC、ConcurrentHashMap等。 温馨提醒: 涉及到AQS重要方法、lock、unlock、CountDownLatch、await、signal几个重要组件的底层讲解所以内容有点长,嫌啰嗦的可直接看几个流程图即可(原图私信回复lock即可)。2、AQS前置知识点2。1、模板方法 AbstractQueuedSynchronizer是个抽象类,所有用到方法的类都要继承此类的若干方法,对应的设计模式就是模版模式。 模版模式定义:一个抽象类公开定义了执行它的方法的方式模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。 抽象类:publicabstractclassSendCustom{publicabstractvoidto();publicabstractvoidfrom();publicvoiddate(){System。out。println(newDate());}publicabstractvoidsend();注意此处框架方法模板方法publicvoidsendMessage(){to();from();date();send();}} 模板方法派生类:publicclassSendSmsextendsSendCustom{Overridepublicvoidto(){System。out。println(sowhat);}Overridepublicvoidfrom(){System。out。println(xiaomai);}Overridepublicvoidsend(){System。out。println(Sendmessage);}publicstaticvoidmain(String〔〕args){SendCustomsendCnewSendSms();sendC。sendMessage();}}2。2、LookSupport LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,当然阻塞之后肯定得有唤醒的方法。常用方法如下:publicstaticvoidpark(Objectblocker);暂停当前线程publicstaticvoidparkNanos(Objectblocker,longnanos);暂停当前线程,不过有超时时间的限制publicstaticvoidparkUntil(Objectblocker,longdeadline);暂停当前线程,直到某个时间publicstaticvoidpark();无期限暂停当前线程publicstaticvoidparkNanos(longnanos);暂停当前线程,不过有超时时间的限制publicstaticvoidparkUntil(longdeadline);暂停当前线程,直到某个时间publicstaticvoidunpark(Threadthread);恢复当前线程publicstaticObjectgetBlocker(Threadt); 叫park是因为park英文意思为停车。我们如果把Thread看成一辆车的话,park就是让车停下,unpark就是让车启动然后跑起来。 与Object类的waitnotify机制相比,parkunpark有两个优点:以thread为操作对象更符合阻塞线程的直观定义 操作更精准,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程),增加了灵活性。 parkunpark调用的是Unsafe(提供CAS操作)中的native代码。 parkunpark功能在Linux系统下是用的Posix线程库pthread中的mutex(互斥量),condition(条件变量)来实现的。mutex和condition保护了一个counter的变量,当park时,这个变量被设置为0。当unpark时,这个变量被设置为1。2。3、CAS CAS是CPU指令级别实现了原子性的比较和交换(ConmpareAndSwap)操作,注意CAS不是锁只是CPU提供的一个原子性操作指令。 CAS在语言层面不进行任何处理,直接将原则操作实现在硬件级别实现,之所以可以实现硬件级别的操作核心是因为CAS操作类中有个核心类UnSafe类。 关于CAS引发的ABA问题、性能开销问题、只能保证一个共享变量之间的原则性操作问题,以前CAS中写过,在此不再重复讲解。 注意:并不是说CAS一定比SYN好,如果高并发执行时间久,用SYN好,因为SYN底层用了wait()阻塞后是不消耗CPU资源的。如果锁竞争不激烈说明自旋不严重,此时用CAS。3、AQS重要方法 模版方法分为独占式跟共享式,子类根据需要不同调用不同的模版方法(讲解有点多,想看底层可直接下滑到第四章节)。3。1模板方法3。1。1独占式获取3。1。1。1accquire 不可中断获取锁accquire是获取独占锁方法,acquire尝试获取资源,成功则直接返回,不成功则进入等待队列,这个过程不会被线程中断,被外部中断也不响应,获取资源后才再进行自我中断selfInterrupt()。publicfinalvoidacquire(intarg){if(!tryAcquire(arg)acquireQueued(addWaiter(Node。EXCLUSIVE),arg))selfInterrupt();}acquire(arg)tryAcquire(arg)顾名思义,它就是尝试获取锁,需要我们自己实现具体细节,一般要求是:如果该锁没有被另一个线程保持,则获取该锁并立即返回,将锁的保持计数设置为1。 如果当前线程已经保持该锁,则将保持计数加1,并且该方法立即返回。 如果该锁被另一个线程保持,则出于线程调度的目的,禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态,此时锁保持计数被设置为1。addWaiter(Node。EXCLUSIVE) 主要功能是一旦尝试获取锁未成功,就要使用该方法将其加入同步队列尾部,由于可能有多个线程并发加入队尾产生竞争,因此采用compareAndSetTail锁方法来保证同步acquireQueued(addWaiter(Node。EXCLUSIVE),arg) 一旦加入同步队列,就需要使用该方法,自旋阻塞唤醒来不断的尝试获取锁,直到被中断或获取到锁。3。1。1。2acquireInterruptibly 可中断获取锁acquireInterruptibly相比于acquire支持响应中断。1、如果当前线程未被中断,则尝试获取锁。 2、如果锁空闲则获锁并立即返回,state1。 3、如果当前线程已持此锁,state1,并且该方法立即返回。 4、如果锁被另一个线程保持,出于线程调度目的,禁用当前线程,线程休眠ing,除非锁由当前线程获得或者当前线程被中断了,中断后会抛出InterruptedException,并且清除当前线程的已中断状态。 5、此方法是一个显式中断点,所以要优先考虑响应中断。if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt())thrownewInterruptedException();acquireInterruptibly选择interruptedtrue;acquire的选择3。1。1。3tryAcquireNanos 该方法可以被中断,增加了超时则失败的功能。可以说该方法的实现与上述两方法没有任何区别。时间功能上就是用的标准超时功能,如果剩余时间小于0那么acquire失败,如果该时间大于一次自旋锁时间(spinForTimeoutThreshold1000),并且可以被阻塞,那么调用LockSupport。parkNanos方法阻塞线程。 doAcquireNanos内部:nanosTimeoutdeadlineSystem。nanoTime();if(nanosTimeout0L)returnfalse;if(shouldParkAfterFailedAcquire(p,node)nanosTimeoutspinForTimeoutThreshold)LockSupport。parkNanos(this,nanosTimeout);if(Thread。interrupted())thrownewInterruptedException(); 该方法一般会有以下几种情况产生:在指定时间内,线程获取到锁,返回true。 当前线程在超时时间内被中断,抛中断异常后,线程退出。 到截止时间后线程仍未获取到锁,此时线程获得锁失败,不再等待直接返回false。3。1。2共享式获取3。1。2。1acquireSharedpublicfinalvoidacquireShared(intarg){if(tryAcquireShared(arg)0)doAcquireShared(arg);} 该模版方法的工作:调用tryAcquireShared(arg)尝试获得资源,返回值代表如下含义:负数表示失败。 0表示成功,但没有剩余可用资源。 正数表示成功,且有剩余资源。 doAcquireShared作用: 创建节点然后加入到队列中去,这一块和独占模式下的addWaiter代码差不多,不同的是结点的模式是SHARED,在独占模式EXCLUSIVE。3。1。2。2acquireSharedInterruptibly 无非就是可中断性的共享方法publicfinalvoidacquireSharedInterruptibly(longarg)throwsInterruptedException{if(Thread。interrupted())如果线程被中断,则抛出异常thrownewInterruptedException();if(tryAcquireShared(arg)0)如果tryAcquireShared()方法获取失败,则调用如下的方法doAcquireSharedInterruptibly(arg);}3。1。2。3。tryAcquireSharedNanos 尝试以共享模式获取,如果被中断则中止,如果超过给定超时期则失败。实现此方法首先要检查中断状态,然后至少调用一次tryacquireshared(long),并在成功时返回。否则,在成功、线程中断或超过超时期之前,线程将加入队列,可能反复处于阻塞或未阻塞状态,并一直调用tryacquireshared(long)。publicfinalbooleantryAcquireSharedNanos(longarg,longnanosTimeout)throwsInterruptedException{if(Thread。interrupted())thrownewInterruptedException();returntryAcquireShared(arg)0doAcquireSharedNanos(arg,nanosTimeout);}3。1。3独占式释放 独占锁的释放调用unlock方法,而该方法实际调用了AQS的release方法,这段代码逻辑比较简单,如果同步状态释放成功(tryRelease返回true)则会执行if块中的代码,当head指向的头结点不为null,并且该节点的状态值不为0的话才会执行unparkSuccessor()方法。publicfinalbooleanrelease(longarg){if(tryRelease(arg)){Nodehhead;if(h!nullh。waitStatus!0)unparkSuccessor(h);returntrue;}returnfalse;}3。1。4共享式释放 releaseShared首先去尝试释放资源tryReleaseShared(arg),如果释放成功了,就代表有资源空闲出来,那么就用doReleaseShared()去唤醒后续结点。publicfinalbooleanreleaseShared(intarg){if(tryReleaseShared(arg)){doReleaseShared();returntrue;}returnfalse;} 比如CountDownLatch的countDown()具体实现:publicvoidcountDown(){sync。releaseShared(1);}3。2子类需实现方法 子类要实现父类方法也分为独占式跟共享式。3。2。1独占式获取 tryAcquire顾名思义,就是尝试获取锁,AQS在这里没有对其进行功能的实现,只有一个抛出异常的语句,我们需要自己对其进行实现,可以对其重写实现公平锁、不公平锁、可重入锁、不可重入锁protectedbooleantryAcquire(intarg){thrownewUnsupportedOperationException();}3。2。2独占式释放 tryRelease尝试释放独占锁,需要子类实现。protectedbooleantryRelease(longarg){thrownewUnsupportedOperationException();}3。2。3共享式获取 tryAcquireShared尝试进行共享锁的获得,需要子类实现。protectedlongtryAcquireShared(longarg){thrownewUnsupportedOperationException();}3。2。4共享式释放 tryReleaseShared尝试进行共享锁的释放,需要子类实现。protectedbooleantryReleaseShared(longarg){thrownewUnsupportedOperationException();}3。3状态标志位 state因为用volatile修饰保证了我们操作的可见性,所以任何线程通过getState()获得状态都是可以得到最新值,但是setState()无法保证原子性,因此AQS给我们提供了compareAndSetState方法利用底层UnSafe的CAS功能来实现原子性。privatevolatilelongstate;protectedfinallonggetState(){returnstate;}protectedfinalvoidsetState(longnewState){statenewState;}protectedfinalbooleancompareAndSetState(longexpect,longupdate){returnunsafe。compareAndSwapLong(this,stateOffset,expect,update);}3。4查询是否独占模式 isHeldExclusively该函数的功能是查询当前的工作模式是否是独占模式。需要子类实现。protectedbooleanisHeldExclusively(){thrownewUnsupportedOperationException();}3。5自定义实现锁 这里需要重点说明一点,JUC中一般是用一个子类继承自Lock,然后在子类中定义一个内部类来实现AQS的继承跟使用。publicclassSowhatLockimplementsLock{privateSyncsyncnewSync();Overridepublicvoidlock(){sync。acquire(1);}OverridepublicbooleantryLock(){returnfalse;}OverridepublicbooleantryLock(longtime,TimeUnitunit)throwsInterruptedException{returnsync。tryAcquireNanos(1,unit。toNanos(time));}Overridepublicvoidunlock(){sync。release(1);}OverridepublicConditionnewCondition(){returnsync。newCondition();}OverridepublicvoidlockInterruptibly()throwsInterruptedException{}privateclassSyncextendsAbstractQueuedSynchronizer{OverrideprotectedbooleantryAcquire(intarg){assertarg1;if(compareAndSetState(0,1)){setExclusiveOwnerThread(Thread。currentThread());returntrue;}returnfalse;}OverrideprotectedbooleantryRelease(intarg){assertarg1;if(!isHeldExclusively()){thrownewIllegalMonitorStateException();}setExclusiveOwnerThread(null);setState(0);returntrue;}OverrideprotectedbooleanisHeldExclusively(){returngetExclusiveOwnerThread()Thread。currentThread();}ConditionnewCondition(){returnnewConditionObject();}}} 自定义实现类:publicclassSoWhatTest{publicstaticintm0;publicstaticCountDownLatchlatchnewCountDownLatch(50);publicstaticLocklocknewSowhatLock();publicstaticvoidmain(String〔〕args)throwsException{Thread〔〕threadsnewThread〔50〕;for(inti0;ithreads。length;i){threads〔i〕newThread((){try{lock。lock();for(intj0;j100;j){m;}}finally{lock。unlock();}latch。countDown();});}for(Threadt:threads)t。start();latch。await();System。out。println(m);}}4、AQS底层4。1CLH CLH(Craig、Landin、Hagerstenlocks三个人名字综合而命名):是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。 CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。4。2Node CLH队列由Node对象组成,其中Node是AQS中的内部类。staticfinalclassNode{标识共享锁staticfinalNodeSHAREDnewNode();标识独占锁staticfinalNodeEXCLUSIVEnull;前驱节点volatileNodeprev;后继节点volatileNodenext;获取锁失败的线程保存在Node节点中。volatileThreadthread;当我们调用了Condition后他也有一个等待队列NodenextWaiter;在Node节点中一般通过waitStatus获得下面节点不同的状态,状态对应下方。volatileintwaitStatus;staticfinalintCANCELLED1;staticfinalintSIGNAL1;staticfinalintCONDITION2;staticfinalintPROPAGATE3; waitStatus有如下5中状态:CANCELLED1 表示当前结点已取消调度。当超时或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。SIGNAL1 表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。CONDITION2 表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。PROPAGATE3 共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。INITIAL0 新结点入队时的默认状态。4。3AQS实现4。3。1公平锁和非公平锁 银行售票窗口营业中:公平排队:每个客户来了自动在最后面排队,轮到自己办理业务的时候拿出身份证等证件取票。 非公平排队:有个旅客火车马上开车了,他拿着自己的各种证件着急这想跟窗口工作人员说是否可以加急办理下,可以的话则直接办理,不可以的话则去队尾排队去。 在JUC中同样存在公平锁跟非公平锁,一般非公平锁效率好一些。因为非公平锁状态下打算抢锁的线程不用排队挂起了。4。3。2AQS细节 AQS内部维护着一个FIFO的队列,即CLH队列,提供先来先服务的公平性。AQS的同步机制就是依靠CLH队列实现的。CLH队列是FIFO的双端双向链表队列(方便尾部节点插入)。线程通过AQS获取锁失败,就会将线程封装成一个Node节点,通过CAS原子操作插入队列尾。当有线程释放锁时,会尝试让队头的next节点占用锁,个人理解AQS具有如下几个特点:在AQS同步队列中1表示线程在睡眠状态 当前Node节点线程会把前一个Node。ws1。当前节点把前面节点ws设置为1,你可以理解为:你自己能知道自己睡着了吗?只能是别人看到了发现你睡眠了! 持有锁的线程永远不在队列中。 在AQS队列中第二个才是最先排队的线程。 如果是交替型任务或者单线程任务,即使用了Lock也不会涉及到AQS队列。 不到万不得已不要轻易park线程,很耗时的!所以排队的头线程会自旋的尝试几个获取锁。 4。4加锁跟解锁流程图 以最经典的ReentrantLock为例逐步分析下lock跟unlock底层流程图(要原图的话私信回复:lock)。privateLocklocknewReentrantLock();publicvoidtest(){lock。lock();try{doSomeThing();}catch(Exceptione){。。。}finally{lock。unlock();}} 4。4。1独占式加入同步队列 同步器AQS中包含两个节点类型的引用:一个指向头结点的引用(head),一个指向尾节点的引用(tail),如果加入的节点是OK的则会直接运行该节点,当若干个线程抢锁失败了那么就会抢着加入到同步队列的尾部,因为是抢着加入这个时候用CAS来设置尾部节点。入口代码:publicfinalvoidacquire(intarg){if(!tryAcquire(arg)acquireQueued(addWaiter(Node。EXCLUSIVE),arg))selfInterrupt();}tryAcquire 该方法是需要自我实现的,在上面的demo中可见一斑,就是返回是否获得了锁。protectedfinalbooleantryAcquire(intacquires){finalThreadcurrentThread。currentThread();intcgetState();if(c0){是否需要加入队列,不需要的话则尝试CAS获得锁,获得成功后设置当前锁的拥有者if(!hasQueuedPredecessors()compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);returntrue;}}elseif(currentgetExclusiveOwnerThread()){这就是可重入锁的实现intnextccacquires;if(nextc0)thrownewError(Maximumlockcountexceeded);setState(nextc);returntrue;}returnfalse;}addWaiter(Node。EXCLUSIVE,arg)如果尝试获取同步状态失败的话,则构造同步节点(独占式的Node。EXCLUSIVE),通过addWaiter(Nodenode,intargs)方法将该节点加入到同步队列的队尾。privateNodeaddWaiter(Nodemode){用当前线程构造一个Node对象,mode是一个表示Node类型的字段,或者说是这个节点是独占的还是共享的NodenodenewNode(Thread。currentThread(),mode);将目前队列中尾部节点给predNodepredtail;队列不为空的时候if(pred!null){node。prevpred;先尝试通过AQS方式修改尾节点为最新的节点,如果修改失败,意味着有并发,if(compareAndSetTail(pred,node)){pred。nextnode;returnnode;}}第一次尝试添加尾部失败说明有并发,此时进入自旋enq(node);returnnode;}自旋enq方法将并发添加节点的请求通过CAS跟自旋将尾节点的添加变得串行化起来。说白了就是让节点放到正确的队尾位置。这里进行了循环,如果此时存在了tail就执行同上一步骤的添加队尾操作,如果依然不存在,就把当前线程作为head结点。插入节点后,调用acquireQueued()进行阻塞privateNodeenq(finalNodenode){for(;;){Nodettail;if(tnull){Mustinitializeif(compareAndSetHead(newNode()))tailhead;}else{node。prevt;if(compareAndSetTail(t,node)){t。nextnode;returnt;}}}}acquireQueued是当前Node节点线程在死循环中获取同步状态,而只有前驱节点是头节点才能尝试获取锁,原因是:头结点是成功获取同步状态(锁)的节点,而头节点的线程释放了同步状态以后,将会唤醒其后继节点,后继节点的线程被唤醒后要检查自己的前驱节点是否为头结点。 维护同步队列的FIFO原则,节点进入同步队列之后,会尝试自旋几次。finalbooleanacquireQueued(finalNodenode,intarg){booleanfailedtrue;try{booleaninterruptedfalse;自旋检查当前节点的前驱节点是否为头结点,才能获取锁for(;;){获取节点的前驱节点finalNodepnode。predecessor();if(pheadtryAcquire(arg)){节点中的线程循环的检查,自己的前驱节点是否为头节点只有当前节点前驱节点是头节点才会再次调用我们实现的方法tryAcquire接下来无非就是将当前节点设置为头结点,移除之前的头节点setHead(node);p。nextnull;helpGCfailedfalse;returninterrupted;}否则检查前一个节点的状态,看当前获取锁失败的线程是否要挂起if(shouldParkAfterFailedAcquire(p,node)如果需要挂起,借助JUC包下面的LockSupport类的静态方法park挂起当前线程,直到被唤醒parkAndCheckInterrupt())interruptedtrue;两个判断都是true说明则置true}}finally{如果等待过程中没有成功获取资源(如timeout,或者可中断的情况下被中断了),那么取消结点在队列中的等待。if(failed)取消请求,将当前节点从队列中移除cancelAcquire(node);}} 如果成功就返回,否则就执行shouldParkAfterFailedAcquire、parkAndCheckInterrupt来达到阻塞效果。shouldParkAfterFailedAcquire第二步的addWaiter()构造的新节点,waitStatus的默认值是0。此时,会进入最后一个if判断,CAS设置pred。waitStatusSIGNAL,最后返回false。由于返回false,第四步的acquireQueued会继续进行循环。假设node的前继节点pred仍然不是头结点或锁获取失败,则会再次进入shouldParkAfterFailedAcquire()。上一轮循环中已经将pred。waitStatu1了,则这次会进入第一个判断条件,直接返回true,表示应该阻塞调用parkAndCheckInterrupt。 那么什么时候会遇到ws0呢?当pred所维护的获取请求被取消时(也就是node。waitStatusCANCELLED,这时就会循环移除所有被取消的前继节点pred,直到找到未被取消的pred。移除所有被取消的前继节点后,直接返回false。privatestaticbooleanshouldParkAfterFailedAcquire(Nodepred,Nodenode){intwspred。waitStatus;获得前驱节点的状态if(wsNode。SIGNAL)此处是第二次设置returntrue;if(ws0){do{node。prevpredpred。prev;}while(pred。waitStatus0);pred。nextnode;}else{此处是第一次设置unsafe级别调用设置compareAndSetWaitStatus(pred,ws,Node。SIGNAL);}returnfalse;}parkAndCheckInterrupt主要任务是暂停当前线程然后查看是否已经暂停了。privatefinalbooleanparkAndCheckInterrupt(){调用park()使线程进入挂起状态,什么时候调用了unpark再继续执行下面LockSupport。park(this);如果被唤醒,查看自己是不是已经被中断了。returnThread。interrupted();}cancelAcquireacquireQueued方法的finally会判断failed值,正常运行时候自旋出来的时候会是false,如果中断或者timeout了则会是true,执行cancelAcquire,其中核心代码是node。waitStatusNode。CANCELLED。selfInterruptstaticvoidselfInterrupt(){Thread。currentThread()。interrupt();}4。4。2独占式释放队列头节点 release()会调用tryRelease方法尝试释放当前线程持有的锁,成功的话唤醒后继线程,并返回true,否则直接返回false。publicfinalbooleanrelease(longarg){if(tryRelease(arg)){Nodehhead;if(h!nullh。waitStatus!0)unparkSuccessor(h);returntrue;}returnfalse;}tryRelease这个是子类需要自我实现的,没啥说的根据业务需要实现。unparkSuccessor唤醒头结点的后继节点。privatevoidunparkSuccessor(Nodenode){intwsnode。waitStatus;获得头节点状态if(ws0)如果头节点装小于0则将其置为0compareAndSetWaitStatus(node,ws,0);Nodesnode。next;这个是新的头节点if(snulls。waitStatus0){如果新头节点不满足要求snull;for(Nodettail;t!nullt!node;tt。prev)从队列尾部开始往前去找最前面的一个waitStatus小于0的节点if(t。waitStatus0)st;}if(s!null)唤醒后继节点对应的线程LockSupport。unpark(s。thread);}4。4。3AQS中增加跟删除形象图 5、CountDownLatch底层5。1共享锁CountDownLatch底层 CountDownLatch虽然相对简单,但也实现了共享锁模型。但是如何正确的吹逼CountDownLatch呢?如果在理解了上述流程的基础上,从CountDownLatch入手来看AQS中关于共享锁的代码还比较好看懂,在看的时候可以以看懂大致内容为主,学习其设计的思路,不要陷入所有条件处理细节中,多线程环境中,对与错有时候不是那么容易看出来的。个人追源码绘制了如下图: 5。2计数信号量Semaphore Semaphore这就是共享锁的一个实现类,在初始化的时候就规定了共享锁池的大小N,有一个线程获得了锁,可用数就减少1个。有一个线程释放锁可用数就增加1个。如果有2的线程同时释放锁,则此时有多个锁可用。这个时候就可以同时唤醒两个锁setHeadAndPropagate(流程图懒的绘制了)。publicfinalvoidacquireShared(intarg){if(tryAcquireShared(arg)0)doAcquireShared(arg);}privatevoiddoAcquireShared(intarg){finalNodenodeaddWaiter(Node。SHARED);booleanfailedtrue;try{booleaninterruptedfalse;for(;;){找先驱结点finalNodepnode。predecessor();if(phead){尝试获取资源intrtryAcquireShared(arg);if(r0){设置当前结点为头结点,然后去唤醒后续结点。注意传播性唤醒!setHeadAndPropagate(node,r);p。nextnull;helpGC释放头结点,等待GCif(interrupted)selfInterrupt();failedfalse;获取到资源return;}}if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt())interruptedtrue;}}finally{if(failed)如果最后没有获取到资源,则cancelcancelAcquire(node);}}5。3ReentrantReadWriteLock 在ReentrantReadWriteLock类中也是只有一个32位的intstate来表示读锁跟写锁,如何实现的?后16位用来保存独享的写锁个数,第一次获得就是01,第二次重入就是10了,这样的方式来保存。 但是多个线程都可以获得读锁,并且每个线程可能读多次,如何保存?我们用前16位来保存有多少个线程获得了读锁。 每个读锁线程获得的重入读锁个数由内部类HoldCounter与读锁配套使用。6、Condition synchronized可用wait()和notify()notifyAll()方法相结合可以实现等待通知模式。Lock也提供了Condition来提供类似的功能。 Condition是JDK5后引入的Interface,它用来替代传统的Object的wait()notify()实现线程间的协作,相比使用Object的wait()notify(),使用Condition的await()signal()这种方式实现线程间协作更加安全和高效。简单说,他的作用是使得某些线程一起等待某个条件(Condition),只有当该条件具备(signal或者signalAll方法被调用)时,这些等待线程才会被唤醒,从而重新争夺锁。wait()notify()这些都更倾向于底层的实现开发,而Condition接口更倾向于代码实现的等待通知效果。两者之间的区别与共通点如下: 6。1条件等待队列 条件等待队列,指的是Condition内部自己维护的一个队列,不同于AQS的同步等待队列。它具有以下特点:要加入条件等待队列的节点,不能在同步等待队列。 从条件等待队列移除的节点,会进入同步等待队列。 一个锁对象只能有一个同步等待队列,但可以有多个条件等待队列。 这里以AbstractQueuedSynchronizer的内部类ConditionObject为例(Condition的实现类)来分析下它的具体实现过程。首先来看该类内部定义的几个成员变量:Firstnodeofconditionqueue。privatetransientNodefirstWaiter;Lastnodeofconditionqueue。privatetransientNodelastWaiter; 它采用了AQS的Node节点构造(前面说过Node类有nextWaiter属性),并定义了两个成员变量:firstWaiter、lastWaiter。说明在ConditionObject内部也维护着一个自己的单向等待队列。目前可知它的结构如下: 6。2await、signal 比如有线程1、2竞争锁,下面来说下具体过程线程1:1、线程1调用reentrantLock。lock时,持有锁。 2、线程1调用await方法,进入条件等待队列,同时释放锁。 3、线程1获取到线程2signal信号,从条件等待队列进入同步等待队列。 线程2:1、线程2调用reentrantLock。lock时,由于锁被线程1持有,进入同步等待队列。 2、由于线程1释放锁,线程2从同步等待队列移除,获取到锁。 3、线程2调用signal方法,导致线程1被唤醒。线程2调用unlock,线程1获取锁后继续下走。6。2。1await 当我们看await、signal的源码时候不要认为等待队列跟同步队列是完全分开的,其实个人感觉底层源码是有点HashMap中的红黑树跟双向链表的意思。 当调用await方法时候,说明当前任务队列的头节点拿着锁呢,此时要把该Thread从任务队列挪到等待队列再唤醒任务队列最前面排队的运行任务,如图: thread表示节点存放的线程。waitStatus表示节点等待状态。条件等待队列中的节点等待状态都是CONDITION,否则会被清除。nextWaiter表示后指针。6。2。2signal 当我们调用signal方法的时候,我们要将等待队列中的头节点移出来,让其去抢锁,如果是公平模式就要去排队了,流程如图: 上面只是形象流程图,如果从代码级别看的话大致流程如下: 6。2。3signalAll signalAll与signal方法的区别体现在doSignalAll方法上,前面我们已经知道doSignal方法只会对等待队列的头节点进行操作,doSignalAll方法只不过将等待队列中的每一个节点都移入到同步队列中,即通知当前调用condition。await()方法的每一个线程:privatevoiddoSignalAll(Nodefirst){lastWaiterfirstWaiternull;do{Nodenextfirst。nextWaiter;first。nextWaiternull;transferForSignal(first);firstnext;}while(first!null);循环}6。3End 一个Condition对象就有一个单项的等待任务队列。在一个多线程任务中我们可以new出多个等待任务队列。比如我们new出来两个等待队列。privateLocklocknewReentrantLock();privateConditionFirstCondlock。newCondition();privateConditionSecondCondlock。newCondition(); 所以真正的AQS任务中一般是一个任务队列N个等待队列的,因此我们尽量调用signal而少用signalAll,因为在指定的实例化等待队列中只有一个可以拿到锁的。 Synchronized中的wait跟notify底层代码的等待队列只有一个,多个线程调用wait的时候我们是无法知道头节点是那个具体线程的。因此只能notifyAll。7、参考1、详解Condition的await和signal:https:www。jianshu。comp28387056eeb4 2、Condition的await和signal流程:https:www。cnblogs。cominsaneXsp12219097。html 链接:https:mp。weixin。qq。comsXxhBE5tb1uuIp8LnMQoPZQ