专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

JUC并发编程02AQS源码剖析

  1。AQS介绍
  相信每个JavaCoder都使用过或者至少听说过AQS,它是抽象队列同步器AbstractQueuedSynchronizer的简称,在juc包下。它提供了一套可用于实现锁同步机制的标准框架,其维护了一个volatile修饰的共享变量state和一个FIFO(先进先出)线程等待队列,多线程争用资源被阻塞的时候就会进入这个队列。state是共享变量,其访问方式有如下三种:getState(),setState(),compareAndSetState(),通过尝试获取共享变量state的结果来对线程的状态作出处理。
  基于JDK8分析
  我们再简单看下AbstractQueuedSynchronizer类结构:publicabstractclassAbstractQueuedSynchronizerextendsAbstractOwnableSynchronizerimplementsjava。io。Serializable{privatestaticfinallongserialVersionUID7373984972572414691L;Createsanew{codeAbstractQueuedSynchronizer}instancewithinitialsynchronizationstateofzero。protectedAbstractQueuedSynchronizer(){}Waitqueuenodeclass。pToenqueueintoaCLHlock,youatomicallyspliceitinasnewtail。Todequeue,youjustsettheheadfield。preprevheadtailprestaticfinalclassNode{共享节点staticfinalNodeSHAREDnewNode();独占节点staticfinalNodeEXCLUSIVEnull;取消排队staticfinalintCANCELLED1;唤醒后继节点staticfinalintSIGNAL1;这个和Condition相关staticfinalintCONDITION2;waitStatusvaluetoindicatethenextacquireSharedshouldunconditionallypropagatestaticfinalintPROPAGATE3;CLH队列节点Node的等待状态默认值是0取值就是上面的1,1,2,3volatileintwaitStatus;CLH队列节点Node的上一个节点volatileNodeprev;CLH队列节点Node的下一个节点volatileNodenext;CLH队列节点Node维护的ThreadvolatileThreadthread;。。。。。。。。}AQS内部维护的CLH队列中的头节点privatetransientvolatileNodehead;AQS内部维护的CLH队列中的尾节点privatetransientvolatileNodetail;同步状态privatevolatileintstate;。。。。。。省略其他code。。。。。。。。}复制代码
  我们用一张图来概括下:
  2。AQS原理
  AQS是将暂时无法请求共享资源的线程封装成一个CLH队列(虚拟的双向队列)的一个结点来实现锁的分配。根据volatile修饰的state共享变量,线程通过CAS(Compareandswap)去改变状态。如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现,即将暂时获取不到锁的线程加入到队列中等待被唤醒。
  3。AQS的实现
  AQS(AbstractQueuedSynchronizer)它是整个同步机制的基类,它是基于模板方法模式进行设计的,如果需要实现同步器,一般可以这样:使用者继承AbstractQueuedSynchronizer并重写指定的方法将AQS组合在同步组件的实现中,并调用其模板方法(这些模板方法会调用使用者重写的方法)
  同步器在实现的时候只需要实现共享资源state的获取和释放方式即可,具体线程等待队列的维护,AQS已经实现。同步器实现的时候主要关注下面几个方法:
  方法
  说明
  tryAcquire(int)
  独占方式。尝试获取资源,成功则返回true,失败则返回false
  tryRelease(int)
  独占方式。尝试释放资源,成功则返回true,失败则返回false
  tryAcquireShared(int)
  共享方式。尝试获取资源。负数表示失败;大于等于0表示成功,其中0表示没有剩余可用资源
  tryReleaseShared(int)
  tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false
  一般来说实现同步器要么是独占,要么是共享方式,只需实现tryAcquiretryRelease或者tryAcquireSharedtryReleaseShared中的一种即可。
  虽然AOS也支持同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock,但是使用情况较少
  其中在acquire(),acquireShared()两种方式下,线程在等待队列中忽略中断,acquirelnterruptibly(),acquireSharedlnterruptibly()支持响应中断,一旦中断将抛出中断异常。3。1独占or共享?
  实现了AQS的常见锁有:ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier、ReentrantReadWritelock都是AQS的衍生物。
  资源共享方式
  典型实现类
  独占
  只有一个线程能操作共享资源,如ReentrantLock
  共享
  多个线程可以同时操作共享资源,如Semaphore,CountDownLatch,CyclicBarrier
  独占和共享
  ReentrantReadWritelock,读锁是共享的,写锁是独占的
  好了,关于AQS到这里已经有了基本的认识,接下来我们就从ReentrantLock着手,一步一步分析AQS的源码。4。AQS源码分析4。1认识ReentrantLock
  想必ReentrantLock这个类或多或少都使用过,这里我们就先简单看下ReentrantLock类的类结构publicclassReentrantLockimplementsLock,java。io。Serializable{privatestaticfinallongserialVersionUID7373984872572414699L;同步器privatefinalSyncsync;同步器父类:继承了AQSabstractstaticclassSyncextendsAbstractQueuedSynchronizer{privatestaticfinallongserialVersionUID5179523762034025860L;加锁,留给子类实现abstractvoidlock();非公平尝试获取锁finalbooleannonfairTryAcquire(intacquires){finalThreadcurrentThread。currentThread();intcgetState();if(c0){if(compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);returntrue;}}elseif(currentgetExclusiveOwnerThread()){intnextccacquires;Integer。MAXVALUE10if(nextc0)overflowthrownewError(Maximumlockcountexceeded);setState(nextc);returntrue;}returnfalse;}尝试释放锁protectedfinalbooleantryRelease(intreleases){intcgetState()releases;if(Thread。currentThread()!getExclusiveOwnerThread())thrownewIllegalMonitorStateException();booleanfreefalse;if(c0){freetrue;setExclusiveOwnerThread(null);}setState(c);returnfree;}。。。。。。省略其他。。。。。。}非公平同步器,继承了SyncstaticfinalclassNonfairSyncextendsSync{privatestaticfinallongserialVersionUID7316153563782823691L;加锁finalvoidlock(){if(compareAndSetState(0,1))setExclusiveOwnerThread(Thread。currentThread());elseacquire(1);}protectedfinalbooleantryAcquire(intacquires){returnnonfairTryAcquire(acquires);}}公平同步器,继承了SyncstaticfinalclassFairSyncextendsSync{privatestaticfinallongserialVersionUID3000897897090466540L;加锁finalvoidlock(){acquire(1);}尝试获取锁protectedfinalbooleantryAcquire(intacquires){finalThreadcurrentThread。currentThread();intcgetState();if(c0){if(!hasQueuedPredecessors()compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);returntrue;}}elseif(currentgetExclusiveOwnerThread()){intnextccacquires;if(nextc0)thrownewError(Maximumlockcountexceeded);setState(nextc);returntrue;}returnfalse;}}默认是非公平锁publicReentrantLock(){syncnewNonfairSync();}指定公平或者非公平publicReentrantLock(booleanfair){syncfair?newFairSync():newNonfairSync();}ReentrantLock对外提供的加锁方法publicvoidlock(){sync。lock();}支持中断的加锁,一旦线程中断将抛出异常publicvoidlockInterruptibly()throwsInterruptedException{sync。acquireInterruptibly(1);}ReentrantLock对外提供的尝试获取锁,获取到返回true,否则返回falsepublicbooleantryLock(){returnsync。nonfairTryAcquire(1);}可以指定时间,在指定时间内获取锁返回true,否则返回falsepublicbooleantryLock(longtimeout,TimeUnitunit)throwsInterruptedException{returnsync。tryAcquireNanos(1,unit。toNanos(timeout));}ReentrantLock对外提供的释放锁publicvoidunlock(){sync。release(1);}。。。。。。。省略其他。。。。。。}复制代码
  从类结构中,可以清晰的看到,它内部是基于AQS来实现的。
  我们简单看下类继承关系图:
  4。1。1怎么理解公平和非公平?
  我们看下公平的方式获取锁的代码:
  其实公平和非公平非常好理解,假设张三在银行窗口办理业务,李四和王五在候客区等待,在张三办理完业务的一瞬间,刘华强正好也推门来到银行里办理业务:如果是公平锁,刘华强需要乖乖排队到李四和王五的后面,等他们都办理完了,刘华强再去办理业务如果是非公平锁,刘华强进来后不需要去排队,直接去窗口办理业务,就是这么不礼貌。
  如果刘华强顺利的做到了柜台窗口,那么就表示他获得了锁,可以继续办理业务了,如果在刘华强刚要坐下办理时,来了一个更横的白宝山抢先一步做到了柜台椅子上,此时白宝山办起来业务,刘华强则需要去排队了。
  知道了公平和非公平,那么我们接下来就以非公平展开讨论,ReentrantLock默认的就是非公平。4。2通过ReentrantLock走进AQS
  模拟3个人去银行办理业务publicclassAqsDemo{staticLocklocknewReentrantLock();publicstaticvoidmain(String〔〕args){Threadt1newThread(AqsDemo::bankProcess,张三);t1。start();Threadt2newThread(AqsDemo::bankProcess,李四);Threadt3newThread(AqsDemo::bankProcess,王五);t2。start();t3。start();}模拟银行窗口处理业务privatestaticvoidbankProcess(){lock。lock();try{try{System。out。println(银行柜台开始处理〔Thread。currentThread()。getName()〕的业务);模拟办理业务非常久Thread。sleep(300000L);}catch(InterruptedExceptione){e。printStackTrace();}}finally{lock。unlock();}}}复制代码4。2。1加锁
  张三先办理业务,调用lock。lock()方法获得锁,继续往里走:finalvoidlock(){CAS修改同步状态state的值如果成功将state的值从0修改为1,则表示获取锁成功if(compareAndSetState(0,1))setExclusiveOwnerThread(Thread。currentThread());else否则将尝试加入等待队列(这中间也可能会继续抢锁,后面再说)acquire(1);}复制代码
  由于张三是第一个来办理业务的,所以它可以成功的将state的值从0修改为1,表示张三获得锁,开始办理业务。
  此时李四也过来办理业务,此时发现state1,说明窗口有人在办理,此时李四就尝试去排队了finalvoidlock(){张三正在办理业务,state1if(compareAndSetState(0,1))setExclusiveOwnerThread(Thread。currentThread());else李四将来到这里acquire(1);}复制代码
  继续跟进去看下:arg1publicfinalvoidacquire(intarg){if(!tryAcquire(arg)acquireQueued(addWaiter(Node。EXCLUSIVE),arg))selfInterrupt();}复制代码
  它内部主要包含了3个方法,我们接下来分别看下这3个方法都做了什么?4。2。1。1tryAcquire(arg)方法剖析finalbooleannonfairTryAcquire(intacquires){finalThreadcurrentThread。currentThread();intcgetState();if(c0){if(compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);returntrue;}}elseif(currentgetExclusiveOwnerThread()){intnextccacquires;if(nextc0)overflowthrownewError(Maximumlockcountexceeded);setState(nextc);returntrue;}returnfalse;}复制代码
  ReentrantLock。NonfairSync:非公平锁的具体实现获取同步状态位state(共享资源)的值如果state0,说明共享资源是空闲的,此时通过CAS尝试将其从0修改为1,并标记当前持有锁的线程如果state!0,看下当前线程是否为已经持有锁的线程,如果是,则将state1,表示重入。
  整个过程也比较好理解,首先判断state是否为0,比如李四在去候客区之前又过来看下窗口是否是空闲的,如果是空闲的,他就不用去候客区了,直接办理业务就好了(此时就表示李四获得了锁)
  如果没有获得锁,没有获得锁具体的体现就是无法将state从0修改为1,所以李四在这里将返回false。4。2。1。2addWaiter(Node。EXCLUSIVE)方法剖析mode:Node。EXCLUSIVEprivateNodeaddWaiter(Nodemode){1。创建一个Node节点,里面封装了当前线程NodenodenewNode(Thread。currentThread(),mode);判断尾节点会否为空,首次进来肯定是空的所以李四过来的时候这里是空,将先调用enq方法第一个排队的节点不会来到这里,它要先调用enq方法去初始化头节点等王五过来的时候我们在分析Nodepredtail;if(pred!null){node。prevpred;if(compareAndSetTail(pred,node)){pred。nextnode;returnnode;}}enq(node);returnnode;}复制代码
  继续看下enq(node)方法:node是封装了当前线程的节点privateNodeenq(finalNodenode){死循环for(;;){Nodettail;判断尾节点是否为空,如果为空,必须初始化所以第一次进来(李四过来)的时候讲初始化,初始化的逻辑是设置一个头节点,这个节点没有任何数据,通常称为虚拟节点傀儡节点if(tnull){Mustinitializeif(compareAndSetHead(newNode()))tailhead;}else{node。prevt;将当前节点设置到尾节点if(compareAndSetTail(t,node)){将当前节点和虚拟节点建立联系t。nextnode;returnt;}}}}复制代码
  这个方法也比较好理解,第一次循环过来将设置虚拟头节点,然后继续循环,这次tnull就不成立了,于是将来到else的逻辑,在这里将建立当前节点和虚拟节点的联系
  用一张图来看下:
  到这里,排队节点就建立好双向连接的关系,接下来我们看最后一个方法4。2。1。3acquireQueued(addWaiter(Node。EXCLUSIVE),arg))方法剖析node是封装了李四线程的节点arg1finalbooleanacquireQueued(finalNodenode,intarg){入队失败的标识booleanfailedtrue;try{中断的标识booleaninterruptedfalse;死循环for(;;){获取当前节点的前驱节点finalNodepnode。predecessor();如果前一个节点是head,那么当前节点再真正去候客区前再次尝试获取锁tryAcquire(arg)方法就是我们前面分析的第一个方法,就是尝试获取锁根据我们前面画的那张图可以知道,李四的前驱节点就是head,所以它会再次尝试获取锁,如果张三的业务办理时间特别短,李四获取锁成功则将李四节点置为新的虚拟节点如果张三的业务办理时间特别长,李四获取锁失败,则将调用下面的if方法if(pheadtryAcquire(arg)){setHead(node);p。nextnull;helpGCfailedfalse;returninterrupted;}shouldParkAfterFailedAcquire()方法单独分析if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt())interruptedtrue;}}finally{if(failed)cancelAcquire(node);}}复制代码
  我们先看第一个if的逻辑:finalNodepnode。predecessor();if(pheadtryAcquire(arg)){setHead(node);p。nextnull;helpGCfailedfalse;returninterrupted;}复制代码
  它的逻辑就是:获取当前节点的前驱节点,如果前驱节点是head,那说明当前节点是排在head之后的第一个节点,那么此时当前就去再次尝试获取锁,如果获取锁成功,则将当前节点置为傀儡节点。
  如果当前节点的前驱节点不是head节点或者尝试获取锁失败,则将第二个if的逻辑:if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt()){interruptedtrue;}复制代码
  我们先看第一个方法:shouldParkAfterFailedAcquire(p,node)pred:是当前节点的前驱节点node:当前节点privatestaticbooleanshouldParkAfterFailedAcquire(Nodepred,Nodenode){前驱节点的状态waitStatus的值默认是0,有1,1,2,3几种取值intwspred。waitStatus;如果前驱节点的状态是1,则直接返回true,表示pred节点具有唤醒下一个节点的责任if(wsNode。SIGNAL)returntrue;如果ws0,表示前驱节点已经取消了,还是以银行为例,可能某个在候客区排队的人突然临时有事,就不办理了,此时他就从候客区出去了if(ws0){如果(当前节点的)前驱节点是取消状态,那么我就继续看前驱节点的前驱节点是不是也是取消的直到找到有效节点举个例子:ABCDEF假如当前节点node是E,前驱节点是D,如果D的状态是1(CANCELLED),那么就看C的状态,如果C也是1,那么就看B,如果B的状态不是1,则结构就变成了ABEFdo{node。prevpredpred。prev;}while(pred。waitStatus0);pred。nextnode;}else{状态是小于等于0并且不是1使用CAS将前一个节点的状态修改为1compareAndSetWaitStatus(pred,ws,Node。SIGNAL);}returnfalse;}复制代码
  总的来说,这个方法就是将当前节点的前驱节点的waitStatus状态修改为Node。SIGNAL,而这个状态表示前驱结点具有唤醒下一个节点的能力。
  由于现在CLH队列中只有两个节点:虚拟头节点和李四节点,那么这个方法执行完就变成了这样:
  接下来我们再看第二个方法:parkAndCheckInterrupt()privatefinalbooleanparkAndCheckInterrupt(){1。挂起当前线程LockSupport。park(this);2。返回当前线程是否中断了注意:只有当前线程被unpark()或者当前线程被中断了,才会走到这里,否则将一直阻塞在1处returnThread。interrupted();}复制代码
  这个方法比较简单,首先通过LockSupport的park()方法挂起当前线程,此时到这里,李四线程就是真正的在候客区排队等待了。之后只有被唤醒了才有资格去柜台办理业务。
  我们在回过来后,整体看下这个方法:finalbooleanacquireQueued(finalNodenode,intarg){booleanfailedtrue;try{booleaninterruptedfalse;for(;;){finalNodepnode。predecessor();if(pheadtryAcquire(arg)){setHead(node);p。nextnull;helpGCfailedfalse;returninterrupted;}if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt())interruptedtrue;}}finally{if(failed)cancelAcquire(node);}}复制代码为什么要搭配死循环?acquireQueued()方法的出口在哪里?
  我们知道,同步队列是一个先进先出的队列,headABCDtail,那么A肯定是第一个要被唤醒的线程(唤醒后面我们再看)。我们还是以李四为例,李四的前驱结点是head,但是张三的业务办理时间特别长,所以李四调用tryAcquire(arg)方法依然没有获得锁,那么李四最后将会调用LockSupport。park()方法将自己挂起,阻塞到这里,无法向下执行,所以也不会退出for循环,更不会退出acquireQueued()方法。假设李四被LockSupport。unpark()唤醒了,唤醒之后,将继续执行for循环,再次判断if(pheadtryAcquire(arg))是否成立,其实主要就是判断tryAcquire(arg)方法是否可以获得锁,如果获得锁,则返回true,否则将返回false。如果李四获得锁((pheadtryAcquire(arg))true),那么李四就成为新的head节点(傀儡节点),李四将退出for循环,也就意味着退出acquireQueued()方法,这就表明李四获得了锁,意味着加锁逻辑lock。lock()可以继续往下走了。如果李四没有获得锁((pheadtryAcquire(arg))false),什么情况下李四被唤醒之后没有获得锁呢?这就是前面我们说过的非公平锁,李四被唤醒后去抢锁,可能在抢的一瞬间,有田七过来率先获得了锁,所以李四就没有获得锁,这样李四就需要继续挂起,等待被唤醒。(这就是需要放到死循环中的原因)
  我们继续看王五抢锁(张三办理业务时间特别长,一直没有释放锁,李四已经在候客区排队等待了)
  整个过程和李四基本上是一样的,我们只看下不同的地方:addWaiter(Nodemode)modeNode。EXCLUSIVEprivateNodeaddWaiter(Nodemode){将当前线程(王五)封装到Node节点中NodenodenewNode(Thread。currentThread(),mode);如果pred不是null,说明队列已经初始化过了,所以就不需要进入enq()方法去初始化队列这里只需要使用CAS将当前节点设置到尾部即可,然后返回当前节点Nodepredtail;if(pred!null){node。prevpred;if(compareAndSetTail(pred,node)){pred。nextnode;returnnode;}}enq(node);returnnode;}复制代码
  继续往下看:acquireQueued(finalNodenode,intarg)finalbooleanacquireQueued(finalNodenode,intarg){booleanfailedtrue;try{booleaninterruptedfalse;for(;;){现在王五的前驱结点是李四,李四不是head结点,所以不会进来这里finalNodepnode。predecessor();if(pheadtryAcquire(arg)){setHead(node);p。nextnull;helpGCfailedfalse;returninterrupted;}shouldParkAfterFailedAcquire(p,node):修改前驱结点李四的waitStatus为1,表示具有唤醒后继节点的能力parkAndCheckInterrupt():当前线程挂起if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt())interruptedtrue;}}finally{if(failed)当前线程因为某种原因失败了,则取消排队,基本上不会进来这里cancelAcquire(node);}}复制代码
  到这里,入队的逻辑就基本上都完成了,接下来我们看下唤醒。4。2。2解锁(唤醒)
  张三业务办理结束,需要释放锁,然后去唤醒等待队列中的线程。privatestaticvoidbankProcess(){lock。lock();try{try{System。out。println(银行柜台开始处理〔Thread。currentThread()。getName()〕的业务);Thread。sleep(300000L);}catch(InterruptedExceptione){e。printStackTrace();}}finally{业务办理完毕,释放锁lock。unlock();}}复制代码
  我们继续看下内部逻辑,主要做两件事:当前线程释放锁:将同步状态位state从1置为0唤醒等待队列中的线程去抢锁:用LockSupport。unpark()唤醒LockSupport。park()阻塞的线程publicfinalbooleanrelease(intarg){1。tryRelease(arg)释放锁,释放成功,去唤醒等待中的线程if(tryRelease(arg)){Nodehhead;if(h!nullh。waitStatus!0)2。唤醒等待中的线程unparkSuccessor(h);returntrue;}returnfalse;}复制代码
  首先看下释放锁的逻辑:就是将同步状态位state从1修改为0。releases1protectedfinalbooleantryRelease(intreleases){获取同步状态位的值,然后减一intcgetState()releases;if(Thread。currentThread()!getExclusiveOwnerThread())thrownewIllegalMonitorStateException();booleanfreefalse;如果为0,说明成功释放锁if(c0){freetrue;将独占线程置为nullsetExclusiveOwnerThread(null);}将同步状态位的值从1修改为0setState(c);returnfree;}复制代码
  然后再看下唤醒等待队列中的线程的逻辑node是等待队列中的头结点同步队列是一个FIFO的队列,所以肯定要先从头部唤醒unparkSuccessor方法名直译过来就是唤醒后继结点,head是傀儡节点,它的next才是有效节点privatevoidunparkSuccessor(Nodenode){经过前面的分析,我们知道node(head)的waitStatus是1,所以这里if条件成立然后通过CAS将其修改为0,不明白这里为什么要置为0?intwsnode。waitStatus;if(ws0){compareAndSetWaitStatus(node,ws,0);}获得head节点的下一个节点,这个节点就是李四Nodesnode。next;如果s是null或者waitStatus0,说明下一个节点可能不存在了或者取消排队了此时就从队列的尾部一直往前找,直到找到最接近head的一个有效节点if(snulls。waitStatus0){snull;for(Nodettail;t!nullt!node;tt。prev)if(t。waitStatus0)st;}if(s!null){找到之后,就唤醒等待队列中的线程LockSupport。unpark(s。thread);}}复制代码
  一旦调用了LockSupport。unpark(s。thread)方法,那么同步队列中调用LockSupport。park()方法阻塞的线程将会被唤醒,从阻塞之处开始往下执行。
  我们看下阻塞的代码:finalbooleanacquireQueued(finalNodenode,intarg){booleanfailedtrue;try{booleaninterruptedfalse;for(;;){finalNodepnode。predecessor();if(pheadtryAcquire(arg)){setHead(node);p。nextnull;helpGCfailedfalse;returninterrupted;}parkAndCheckInterrupt()调用LockSupport。park()方法阻塞线程if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt()){interruptedtrue;}}}finally{if(failed)cancelAcquire(node);}}复制代码
  一旦执行LockSupport。park()方法,现成将阻塞到这里,不会往下执行,一直卡在if条件判断里,一旦有人释放锁,然后调用LockSupport。unpark(s。thread)方法唤醒线程,则这里就可以继续往下执行,由于这里是一个for(;;)的死循环,所以它继续循环执行,然后首先执行第一个if判断,看它是否是接挨着head节点(head节点是虚拟节点,不保存线程)的节点(先进先出的体现),然后尝试获取锁(将同步状态位state从0修改为1)一旦获取锁,则存储当前线程的Node节点将成为新的虚拟头节点,然后退出acquireQueued(finalNodenode,intarg)方法,一直向上退出,最终来到我们自己编写的加锁代码lock。lock()处,然后就可以继续往下执行业务逻辑了。
  我们把其中的代码用图标注下:
  到这里,整个AQS的核心流程就结束了,其实只要一步一步仔细的梳理代码,发现也并不难理解O()O哈哈5。总结
  我们简单梳理下AQS的加锁,排队,唤醒的流程:通过CAS将同步状态位state从0修改为1,如果修改成功,表示获得锁;如果修改失败,则表示锁被其他线程占有,此时需要排队排队的逻辑就是将当前线程封装到Node节点中,然后尾插法插入到CLH同步队列的尾部,然后调用LockSupport。park()阻塞当前线程获得锁的线程释放锁后,将会调用LockSupport。unpark()方法从头部开始唤醒阻塞的线程(先进先出的体现)
  好了,关于AQS的介绍就到这里吧,后面有问题再补充。

时评有可能是一条令中国球迷心碎的消息无事儿浏览网页,突看这样一条新闻贾秀全被任命为中国女足形象推广大使!我靠!这新闻肯定是假的!看到此新闻的第一个反应,就是从嘴中不由自主冒出这样一句话。相信喜欢足球的人,没有不知道贾LCK天王山之战,前期DK略优,T1下路会不会继续搞科研将成重点!2023LCK春季常规赛,今天迎来可能是本赛季最重要的一场,那就是T1对阵DK,此前两支队伍就是经常在决赛交手的对手,所以这一战也可以说是今年春季赛决赛的预演了。不过相比T1,今年经开区企业隆基绿能与万华化学签署战略合作协议近日,经开区企业隆基绿能科技股份有限公司(以下称隆基绿能)与万华化学集团股份有限公司(以下称万华化学)签署战略合作协议。未来双方将以国家2030年前碳达峰2060年前碳中和目标为指C罗祝福中国球迷被批,背后事情细思极恐春节即农历新年,来源于中国,是中华民族传承几千年的重要节日。在这个喜气洋洋的日子,全世界的中国人及华人华侨都会用自己的方式来庆祝新年到来。可偏偏有人搞事情,前有EA旗下的足球游戏F唏嘘!前中超亿元先生失业,靠直播捞金,自称有望加盟大连人金元足球时代的中超联赛曾经出现过不少荒唐事,一些急功近利的俱乐部为了迅速提高球队的成绩,不惜投入与球员身价根本不相称的资金来为球队引援。在这样的大背景下,20162018赛季的中超科学家尝试使用人工智能来驯服量子系统控制一个篮球的轨迹是相对简单的,因为它只需要应用机械力和人的技能。然而,控制像原子和电子这样的量子系统的运动则构成了更大的挑战。这些微小的粒子很容易受到扰动,从而导致它们以意想不到化学家提出利用拉曼光谱来控制啤酒的质量啤酒腐败菌是啤酒制造商面临的一个问题。现在,中国研究人员想出了一种在整个酿造过程中实时实时检测有害微生物的快速方法。将不需要的微生物排除在啤酒之外是使啤酒美味的必要条件几千年来,人Nature封面新的一年,科研还能不能有新突破?本文来自微信公众号XMOLNews从1900到1950年,科学理论和工程技术的进步是令人瞠目的。这半个世纪中出现的重大进展,几乎没有哪一项是在1900年可以预见的生物学家们发现了D两人缘尽后,无需主动断联文花妖馆缘分来临的时候挡也挡不住,缘分将尽的时候留也留不了。有的人在两人缘尽后会选择断联,试图与对方做一个了断。但在深夜,你一遍遍思念对方的时候,早已证明你输得彻底。很多缘分尽了,自己打伞,才不会淋湿(此文必读)最近,我刷到了一句特别触动我心的话因为自己淋过雨,所以总想替别人撑把伞。人生多舛,命运多艰。有太多辛酸只能有自己来扛,自己体会。我们都渴望在自己孤立无援的时候有人拉一把。但更多的时你若负我,我定成魔祝你生生世世,永失所爱!她怀有身孕含泪离去。殿下,娘娘百年前为救您舍了命珠,她死前舍身献祭,早已道消魂散五千年前,他对天发誓生生世世永不负她五千年后,她身穿嫁衣跨越千山万水而来,换
女性月经期需要注意什么月经是指伴随卵巢周期性变化而出现的子宫内膜周期性脱落及出血,规律月经的出现是女性生殖功能成熟的重要标志,属于生育期女性的生理现象。月经期间女性朋友的身体抵抗力会有所减弱,需要注意很可充电也可换电,价格比蔚来更有优势,睿蓝9这次真的稳了?最近有打算买纯电SUV的朋友们注意了,睿蓝汽车即将上新,全新睿蓝9要来了。睿蓝9此前角师傅已经对其进行了视频拍摄,感兴趣的可以关注角师傅后自行查阅。睿蓝9的最大优势在于它不仅可以充曾黎上海机场街拍穿搭,黑色皮夹克搭配喇叭裤,帅气又时髦文浅若最近,曾黎现身上海机场,这一次的机场街拍穿搭,曾黎穿了一件黑色皮夹克,搭配了一条喇叭裤,脚上穿的是厚底皮靴,最后来一个黑色包包,整个造型看起来帅气时尚,不失时髦感。黑色的皮夹今年流行的毛衣长这样!每一款都好看时髦嗨,各位小仙女们,大家好呀!像毛衣这种单品,真的是秋冬衣橱里的必备品,那么百搭且耐看的款式,相信很多女性们都不会错过吧?如果你也正十分钟情于毛衣,那么接下来Lily的分享,就不要错适合出席年会穿的look,时髦又亮眼,撑起你整个人的气质前段时间,国外有人在滑冰场拍到我们的好朋友谷爱凌与一名男子亲吻。被亲后,谷爱凌害羞地转身溜走的样子,隔着屏幕都能感受到俩人散发的粉红泡泡好吧!简直苏到我心巴上了。有网友猜测,获得了花环夫人女星Pang再婚,富商老公高大有型,2岁女儿当花童头条创作挑战赛说起经典泰剧的恶毒女配,花环夫人里的花魁童帕拉绝对是不得不提的那一个。童帕拉是怡红院的头牌,在卡农的牵线搭桥下,和女主拉蕴的第一任丈夫尤德勾搭上了。娶了童帕拉做小妾后杨超越从草根女孩到全村希望她的观众缘为什么越来越好赚钱就是为了生活,虽然拿着最高的代言和片酬。但杨超越却从来没有忘记自己曾经吃过的苦。相比于圈内的其他明星艺人,杨超越是妥妥的草根出身。没有资源没有人脉也没有背景。她的成功也不光只是横店20万群演真实现状,美女遍地,懒汉光棍成群,他们将何去何从如果您喜欢这篇文章,请点击右上方的关注。感谢您的支持和鼓励,希望能给您带来舒适的阅读体验。曾几何时,小朋友们的梦想已经在潜移默化中变成了长大以后我想成为明星!我们都知道,荧幕前的明冲上热搜的张珊珊,拥有35家核酸公司,到底是谁近日,张珊珊的名字突然冲上了热搜。从网上搜索,在演艺圈会发现两个张珊珊。第一位张珊珊,1985年出生在武汉,生下来就特别乖,别人家的孩子都在哭哭啼啼,她却不哭,小时候的她,学过电子大瓜!侃爷曝金卡戴珊和保罗有染,并直言自己亲眼所见直接抓奸头条创作挑战赛因为近期大S汪小菲的事情,儒爷也是感叹原来分开的情侣真的很难再和平相处。而在大S和汪小菲后,又一对明星夫妻开撕了。近日,外媒曝出大瓜!据顶级说唱歌手侃爷坎耶韦斯特在社以拍戏之名,行霸凌之举,娱乐圈的假戏真做有多离谱?在这个追求演技的时代,某些演员为了追求真实性,偏偏很极端的做法,让人大跌眼镜。好的演技不是飙演技,而是真情流露,更不是行一己之私,以敬业之名行霸凌之举。参加综艺时秦岚被男演员强迫按
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网