幼儿饰品瑜伽美体用品微软
投稿投诉
微软创意
爱情通信
用品婚姻
爱好看病
美体软件
影音星座
瑜伽周边
星座办公
饰品塑形
搞笑减肥
幼儿两性
智家潮品

聊聊AQS

  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

章若楠真实学历是什么章若楠真实学历是毕业于杭州电子科大,本科学历。出生浙江温州,中国内地影视女演员。章若楠在出演悲伤逆流成河以后可谓大火了一把,悲伤逆流成河里章若楠的表现可谓可圈可点,也就是……对谁是凶手里赵丽颖的演技,剧迷们出现两极分化的评论最近很少看国产剧,犹记得2020年爱奇艺的迷雾剧场出了很多好剧,《十日游戏》《隐秘的角落》《沉默的真相》等好剧。最近爱奇艺的迷雾剧场推出了《谁是凶手》这部悬疑剧,随着该剧……章若楠和许光汉在一起了吗章若楠和许光汉没有在一起,之所以被传在一起是因为在两人为开拍电影你的婚礼在微博上互动频繁的结果。在这部电影里面,章若楠会和许光汉饰演一对非常恩爱的情侣。当时这部电影的官方剧组也……张天爱家境如何张天爱小时候家境不是很好,不过,现状通过张天爱的努力家境应该已经很好了。由于张天爱小的时候家里比较贫穷,但是,身为父母就是把最好的给了孩子,就是让张天爱上了当地最贵的一所私立学……中国最值得一看的景观,被誉为中国旅游胜地四十佳,就在湖北浩浩荡荡的长江水,自唐古拉山奔涌而出,一路高歌猛进,奔流入海,滋养了传承千载的华夏文明,也描绘出一副唯美壮阔的山河画卷,这其中自然包括中国最值得一看的景观三峡。所谓三峡,是指瞿……王大陆张天爱恋情是真的吗王大陆张天爱恋情不是真的。疑似王大陆张天爱恋情曝光,原来据网友提供的画面中可以看到,王大陆和张天爱在一起练习拳击,不过,很快王大陆和张天爱就前后背抱在了一起,还很亲密的样子,而……杨绛不要羡慕任何人的生活,其实谁家的锅底都有灰头号解忧馆不要羡慕任何人的生活,其实谁家的锅底都有灰,不是别人风火无限好,而是他们的一地鸡毛没给别人看,人生没有幸福不幸福,只有知足不知足。温饱无虑就是幸福。无病无灾就是……天才少女刘诗雯我值得这个迟到的冠军这是我的第六次世界锦标赛之旅,也是第三次进入决赛,我如果这次再不拿冠军,可能就没机会了,但我觉得我值得这个冠军,只是它来得稍微有点晚刘诗雯十年磨一剑凤凰终涅槃……太原市迎泽区太原双塔永祚寺入选首批山西文化记忆项目行旅远来,遥见塔影,即知太原将至;公私外出,回首塔身,渐远渐没,难尽依依之怀。作为太原的文化地标,永祚双塔数百年来一直代表着太原文脉兴盛,寄托了古今中外无数游客的情怀。永……张天爱年龄到底多大?张天爱年龄在大家心中是一个谜。之前张天爱的资料显示她是1988年的,之后变成1990年,在之后又变成了1992年,目前资料上已经看不到出生年龄信息这个了。还有人说,再怎么改年龄……张天爱徐开聘什么关系张天爱徐开聘合作关系,传过绯闻。张天爱徐开聘一起拍摄电视剧《我的漂亮朋友》,一起录制《哎呀好身材》,互动十分甜蜜,也让观众陷入了思考,是刻意安排还是感情流露hellip;hel……陈雪凝现任男友是谁截止到2021年7月份网上并未传出陈雪凝有男友的消息,不过,陈雪凝之前是有男友的,叫作陈三,但是后来分手了。其实,就算陈雪凝现在有男友也不会曝光出来,因为,当初陈雪凝和陈……
至少1亿!巴黎引援加速,敲定2大新援,解决2大顽疾,姆巴佩满在新任总监坎波斯上任后,巴黎正在逐步加快补强的速度,北京时间7月13日,根据法国队报透露,巴黎有望在本周末敲定2笔交易,他们将以1亿欧元签下国米的王牌中卫什克里尼亚尔和萨索洛中……字节跳动被曝大量招聘芯片工程师NothingPhone1正式(全球TMT2022年7月13日讯)今日要点:字节跳动被曝大量招聘芯片工程师;NothingPhone1正式发布;阿里云百城计划3年投入10亿元专项资金;腾讯CSIG成立政企业……三伏天将至,中医常说的冬病夏治,适用于哪些人群?三伏天将至,这是一年中最热的时候,也是自然界阳气最旺盛的时候,中医学认为人与天地相参,与日月相应,即人体的阳气与自然界生物的阳气相接,季节的变化直接影响到人的健康。此时,人们应……上火又怕冷,上热下寒体质怎么调?老中医帮您开方暖阳气散寒气您呀,是上热下寒体质,得好好调理。这倒奇了,只听过体质有‘寒’有‘热’,没听说过两个体质还能混在一起的,难道一个人的身体能既是寒性的、又是热性的吗?老刘最近发现自己……不是库明加怀斯曼!勇士正式刮出最强新星,加强版普尔保进轮换202122赛季NBANBA夏季联赛继续进行,一场焦点大战在实力不俗的金州勇士队与纽约尼克斯队之间展开激烈的较量。最终,实力更胜一筹的尼克斯队凭借5人得分上双的更强团队发挥,以……中医应对流感,这几招你应该知道来势汹汹的流感让不少人都中了招,中医认为,感染流感的原因在于具有传染性的时行疫毒邪气侵袭正气不足之人体而致病。与普通感冒相比,流感具有传染性与流行性,病情较重,发病急,全……山东男篮三消息徐长锁确定下课,冠军中锋回归,哈德森确定离队按照日程安排,山东男篮将会在五月初正式集结,接下来球队的调整大幕也即将拉开,球员中该续约的续约,该离队的离队。就在球队即将开始集结之前,山东媒体曝出了一个非常重量的消息,那就是……印度大量进口中国不锈钢?买单出口又是啥今日头条前言:近年来,印度不锈钢进口大幅提升,从印尼和中国进口的增幅显著。2021年印度取消反补贴和反倾销税,并下调不锈钢进口关税后,2021年印度从中国大陆、印尼、中国……坚果的营养价值高吗?生活中,我们经常会听到关于坚果的许多种说法:油脂高、热量高、吃多了会胖;营养价值高,多吃对身体好。那么到底哪种说法是正确的呢?首先,我们知道一点:一种食品营养价值的高低,……王者荣耀常玩英雄及玩法建议说来惭愧,本人作为王者荣耀的5年玩家,却只在一个赛季单排上过王者(本赛季目前还在钻石呆着,想着赛季初等各位大佬上到更高的段位之后自己再打排位估计比较容易),还是在本人大学的时候……寄生虫最多的五大食物,其中一种,大家最近都爱吃,价格还不便宜寄生虫最多的五大食物,其中一种,大家最近都爱吃,价格还不便宜病从口入这句话一点都没错,很多时候,身体出现问题,都是因为吃进去的东西导致的。过敏、拉肚子、肠胃炎、食物中毒、……在湖人场均134,休赛期却没被续约,球迷大概率要选择退役了NBA交易市场已经开启几日了,目前联盟当中很多球队都进行了引援,对于湖人队来说他们目前的动作并不算大。至少在续约层面上他们并没有太多的操作。要知道上赛季效力于湖人队的很多球员目……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网