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

一文带你了解AQS源码分析

  一、AQS简介
  AQS,就是AbstractQueuedSynchronizer,在同步组件的实现中,AQS是核心部分,同步组件的实现者通过使用AQS提供的模板方法实现同步组件语义,AQS则实现了对同步状态的管理,以及对阻塞线程进行排队,等待通知等等一些底层的实现处理。AQS的核心也包括了这些方面:同步队列,独占式锁的获取和释放,共享锁的获取和释放以及可中断锁,超时等待锁获取这些特性的实现,而这些实际上则是AQS提供出来的模板方法,归纳整理如下:
  AbstractQueuedSynchronizer的独占式锁方法如下:独占式获取同步状态,如果获取失败则插入同步队列进行等待;publicfinalvoidacquire(intarg)与acquire方法相同,但在同步队列中进行等待的时候可以检测中断;publicfinalvoidacquireInterruptibly(intarg)在acquireInterruptibly基础上增加了超时等待功能,在超时时间内没有获得同步状态返回false;publicfinalbooleantryAcquireNanos(intarg,longnanosTimeout)释放同步状态,该方法会唤醒在同步队列中的下一个节点publicfinalbooleanrelease(intarg)
  AbstractQueuedSynchronizer的共享式锁方法如下:共享式获取同步状态,与独占式的区别在于同一时刻有多个线程获取同步状态;publicfinalvoidacquireShared(intarg)在acquireShared方法基础上增加了能响应中断的功能;publicfinalvoidacquireSharedInterruptibly(intarg)在acquireSharedInterruptibly基础上增加了超时等待的功能;publicfinalbooleantryAcquireSharedNanos(intarg,longnanosTimeout)共享式释放同步状态publicfinalbooleanreleaseShared(intarg)二、同步队列
  先要了解一下AQS中的同步队列。
  当共享资源被某个线程占有,其他请求该资源的线程就会被阻塞,从而进入同步队列。
  就数据结构而言,队列的实现就是数组或链表的形式。AQS中的同步队列是通过链表实现的。
  在AQS中有一个静态内部类:staticfinalclassNode{指示节点正在共享模式下等待的标记staticfinalNodeSHAREDnewNode();指示节点正在独占模式下等待的标记staticfinalNodeEXCLUSIVEnull;节点从同步队列中取消staticfinalintCANCELLED1;后继节点的线程处于等待状态,如果当前节点释放同步状态会通知后继节点,使得后继节点的线程能够运行;staticfinalintSIGNAL1;当前节点进入等待队列中staticfinalintCONDITION2;表示下一次共享式同步状态获取将会无条件传播下去staticfinalintPROPAGATE3;节点状态volatileintwaitStatus;当前节点(线程)的前驱节点volatileNodeprev;当前节点(线程)的后继节点volatileNodenext;加入同步队列的线程引用volatileThreadthread;等待队列中的下一个节点NodenextWaiter;finalbooleanisShared(){returnnextWaiterSHARED;}finalNodepredecessor()throwsNullPointerException{Nodepprev;if(pnull)thrownewNullPointerException();elsereturnp;}Node(){}Node(Threadthread,Nodemode){UsedbyaddWaiterthis。nextWaitermode;this。threadthread;}Node(Threadthread,intwaitStatus){UsedbyConditionthis。waitStatuswaitStatus;this。threadthread;}}
  从上面可以看出来,AQS的同步队列有前驱结点和后续节点,它是一个双向链表。下面看一个DemopublicclassTest{publicstaticvoidmain(String〔〕args){ReentrantLocklocknewReentrantLock();MyThreadmyThreadnewMyThread(lock);for(inti0;i5;i){newThread(myThread)。start();}}}classMyThreadimplementsRunnable{privatefinalReentrantLocklock;staticintcount;MyThread(ReentrantLocklock){this。locklock;}Overridepublicvoidrun(){lock。lock();System。out。println(countcount);lock。unlock();}}
  实例代码中开启了5个线程,先获取锁之后再睡眠10S中,实际上这里让线程睡眠是想模拟出当线程无法获取锁时进入同步队列的情况。通过debug,当Thread4(在本例中最后一个线程)获取锁失败后进入同步时,AQS时现在的同步队列如图所示:
  Thread0先获得锁后进行睡眠,其他线程(Thread1,Thread2,Thread3,Thread4)获取锁失败进入同步队列,同时也可以很清楚的看出来每个节点有两个域:prev(前驱)和next(后继),并且每个节点用来保存获取同步状态失败的线程引用以及等待状态等信息。另外AQS中有两个重要的成员变量:privatetransientvolatileNodehead;privatetransientvolatileNodetail;
  也就是说AQS实际上通过头尾指针来管理同步队列,同时实现包括获取锁失败的线程进行入队,释放锁时对同步队列中的线程进行通知等核心方法。其示意图如下:
  通过对源码的理解以及做实验的方式,现在我们可以清楚的知道这样几点:节点的数据结构,即AQS的静态内部类Node,节点的等待状态等信息;同步队列是一个双向队列,AQS通过持有头尾指针管理同步队列;
  那么,节点如何进行入队和出队是怎样做的了?实际上这对应着锁的获取和释放两个操作:获取锁失败进行入队操作,获取锁成功进行出队操作。三、独占锁1。独占锁的获取
  独占锁的获取(acquire方法)
  继续通过看源码和debug的方式来看,还是以上面的demo为例,调用lock()方法是获取独占式锁,获取失败就将当前线程加入同步队列,成功则线程执行。而lock()方法实际上会调用AQS的acquire()方法,源码如下:publicfinalvoidacquire(intarg){先看同步状态是否获取成功,如果成功则方法结束返回若失败则先调用addWaiter()方法,再调用acquireQueued()方法if(!tryAcquire(arg)acquireQueued(addWaiter(Node。EXCLUSIVE),arg))selfInterrupt();}
  acquire根据当前获得同步状态成功与否做了两件事情:成功,则方法结束返回失败,则先调用addWaiter()然后在调用acquireQueued()方法。1。1获取同步状态失败,入队操作
  当线程获取独占式锁失败后就会将当前线程加入同步队列,那么加入队列的方式是怎样的了?我们接下来就应该去研究一下addWaiter()和acquireQueued()。addWaiter()源码如下:privateNodeaddWaiter(Nodemode){1。将当前线程构建成Node类型NodenodenewNode(Thread。currentThread(),mode);Trythefastpathofenq;backuptofullenqonfailure2。当前尾节点是否为nullNodepredtail;if(pred!null){2。2将当前节点尾插入的方式插入同步队列中node。prevpred;if(compareAndSetTail(pred,node)){pred。nextnode;returnnode;}}2。1。当前同步队列尾节点为null,说明当前线程是第一个加入同步队列进行等待的线程enq(node);returnnode;}
  分析可以看上面的注释。程序的逻辑主要分为两个部分:当前同步队列的尾节点为null,调用方法enq()插入;当前队列的尾节点不为null,则采用尾插入(compareAndSetTail()方法)的方式入队。
  另外还会有另外一个问题:如果if(compareAndSetTail(pred,node))为false怎么办?会继续执行到enq()方法,同时很明显compareAndSetTail是一个CAS操作,通常来说如果CAS操作失败会继续自旋(死循环)进行重试。
  因此,经过我们这样的分析,enq()方法可能承担两个任务:处理当前同步队列尾节点为null时进行入队操作;如果CAS尾插入节点失败后负责自旋进行尝试。
  那么是不是真的就像我们分析的一样了?只有源码会告诉我们答案,enq()源码如下:privateNodeenq(finalNodenode){for(;;){Nodettail;if(tnull){Mustinitialize1。构造头结点if(compareAndSetHead(newNode()))tailhead;}else{2。尾插入,CAS操作失败自旋尝试node。prevt;if(compareAndSetTail(t,node)){t。nextnode;returnt;}}}}
  在上面的分析中我们可以看出在第1步中会先创建头结点,说明同步队列是带头结点的链式存储结构。带头结点与不带头结点相比,会在入队和出队的操作中获得更大的便捷性,因此同步队列选择了带头结点的链式存储结构。那么带头节点的队列初始化时机是什么?自然而然是在tail为null时,即当前线程是第一次插入同步队列。compareAndSetTail(t,node)方法会利用CAS操作设置尾节点,如果CAS操作失败会在for(;;)for死循环中不断尝试,直至成功return返回为止。因此,对enq()方法可以做这样的总结:在当前线程是第一个加入同步队列时,调用compareAndSetHead(newNode())方法,完成链式队列的头结点的初始化;自旋不断尝试CAS尾插入节点直至成功为止。
  现在我们已经很清楚获取独占式锁失败的线程包装成Node然后插入同步队列的过程了。那么紧接着会有下一个问题:在同步队列中的节点(线程)会做什么事情来保证自己能够有机会获得独占式锁了?带着这样的问题我们就来看看acquireQueued()方法,从方法名就可以很清楚,这个方法的作用就是排队获取锁的过程,源码如下:finalbooleanacquireQueued(finalNodenode,intarg){booleanfailedtrue;try{booleaninterruptedfalse;for(;;){1。获得当前节点的先驱节点finalNodepnode。predecessor();2。当前节点能否获取独占式锁2。1如果当前节点的先驱节点是头结点并且成功获取同步状态,即可以获得独占式锁if(pheadtryAcquire(arg)){队列头指针用指向当前节点setHead(node);释放前驱节点p。nextnull;helpGCfailedfalse;returninterrupted;}2。2获取锁失败,线程进入等待状态等待获取独占式锁if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt())interruptedtrue;}}finally{if(failed)cancelAcquire(node);}}
  程序逻辑通过注释已经标出,整体来看这又是一个自旋的过程(for循环),代码首先获取当前节点的先驱节点,如果先驱节点是头结点的并且成功获得同步状态的时候(if(pheadtryAcquire(arg))),当前节点所指向的线程能够获取锁。反之,获取锁失败进入等待状态。整体示意图为下图:
  1。2获取锁成功,出队操作
  获取锁的节点出队的逻辑是:队列头结点引用指向当前节点setHead(node);释放前驱节点p。nextnull;helpGCfailedfalse;returninterrupted;
  setHead()方法为:privatevoidsetHead(Nodenode){headnode;node。threadnull;node。prevnull;}
  将当前节点通过setHead()方法设置为队列的头结点,然后将之前的头结点的next域设置为null并且pre域也为null,即与队列断开,无任何引用方便GC时能够将内存进行回收。示意图如下:
  那么当获取锁失败的时候会调用shouldParkAfterFailedAcquire()方法和parkAndCheckInterrupt()方法,看看他们做了什么事情。shouldParkAfterFailedAcquire()方法源码为:privatestaticbooleanshouldParkAfterFailedAcquire(Nodepred,Nodenode){intwspred。waitStatus;if(wsNode。SIGNAL)Thisnodehasalreadysetstatusaskingareleasetosignalit,soitcansafelypark。returntrue;if(ws0){Predecessorwascancelled。Skipoverpredecessorsandindicateretry。do{node。prevpredpred。prev;}while(pred。waitStatus0);pred。nextnode;}else{waitStatusmustbe0orPROPAGATE。Indicatethatweneedasignal,butdontparkyet。Callerwillneedtoretrytomakesureitcannotacquirebeforeparking。compareAndSetWaitStatus(pred,ws,Node。SIGNAL);}returnfalse;}
  shouldParkAfterFailedAcquire()方法主要逻辑是使用compareAndSetWaitStatus(pred,ws,Node。SIGNAL)使用CAS将节点状态由INITIAL设置成SIGNAL,表示当前线程阻塞。当compareAndSetWaitStatus设置失败则说明shouldParkAfterFailedAcquire方法返回false,然后会在acquireQueued()方法中for(;;)死循环中会继续重试,直至compareAndSetWaitStatus设置节点状态位为SIGNAL时shouldParkAfterFailedAcquire返回true时才会执行方法parkAndCheckInterrupt()方法,该方法的源码为:privatefinalbooleanparkAndCheckInterrupt(){使得该线程阻塞LockSupport。park(this);returnThread。interrupted();}
  该方法的关键是会调用LookSupport。park()方法(关于LookSupport会在以后的文章进行讨论),该方法是用来阻塞当前线程的。因此到这里就应该清楚了,acquireQueued()在自旋过程中主要完成了两件事情:如果当前节点的前驱节点是头节点,并且能够获得同步状态的话,当前线程能够获得锁该方法执行结束退出;获取锁失败的话,先将节点状态设置成SIGNAL,然后调用LookSupport。park方法使得当前线程阻塞。
  经过上面的分析,独占式锁的获取过程也就是acquire()方法的执行流程如下图所示:
  2。独占锁的释放
  独占锁的释放(release()方法)
  ReentrantLock的unlock()方法,调用的是AQS的release()方法。下面看一下
  独占锁的释放就相对来说比较容易理解了,先来看下源码:publicfinalbooleanrelease(intarg){if(tryRelease(arg)){Nodehhead;if(h!nullh。waitStatus!0)unparkSuccessor(h);returntrue;}returnfalse;}
  这段代码逻辑就比较容易理解了,如果同步状态释放成功(tryRelease返回true)则会执行if块中的代码,当head指向的头结点不为null,并且该节点的状态值不为0的话才会执行unparkSuccessor()方法。unparkSuccessor方法源码:privatevoidunparkSuccessor(Nodenode){Ifstatusisnegative(i。e。,possiblyneedingsignal)trytoclearinanticipationofsignalling。ItisOKifthisfailsorifstatusischangedbywaitingthread。intwsnode。waitStatus;if(ws0)compareAndSetWaitStatus(node,ws,0);Threadtounparkisheldinsuccessor,whichisnormallyjustthenextnode。Butifcancelledorapparentlynull,traversebackwardsfromtailtofindtheactualnoncancelledsuccessor。头节点的后继节点Nodesnode。next;if(snulls。waitStatus0){snull;for(Nodettail;t!nullt!node;tt。prev)if(t。waitStatus0)st;}if(s!null)后继节点不为null时唤醒该线程LockSupport。unpark(s。thread);}
  源码的关键信息请看注释,首先获取头节点的后继节点,当后继节点不为空的时候会调用LookSupport。unpark()方法,该方法会唤醒该节点的后继节点所包装的线程。因此,每一次锁释放后就会唤醒队列中该节点的后继节点所引用的线程,从而进一步可以佐证获得锁的过程是一个FIFO(先进先出)的过程。
  到现在我们终于啃下了一块硬骨头了,通过学习源码的方式非常深刻的学习到了独占式锁的获取和释放的过程以及同步队列。可以做一下总结:线程获取锁失败,线程被封装成Node进行入队操作,核心方法在于addWaiter()和enq(),同时enq()完成对同步队列的头结点初始化工作以及CAS操作失败的重试线程获取锁是一个自旋的过程,当且仅当当前节点的前驱节点是头结点并且成功获得同步状态时,节点出队即该节点引用的线程获得锁,否则,当不满足条件时就会调用LookSupport。park()方法使得线程阻塞释放锁的时候会唤醒后继节点
  总体来说:在获取同步状态时,AQS维护一个同步队列,获取同步状态失败的线程会加入到队列中进行自旋;移除队列(或停止自旋)的条件是前驱节点是头结点并且成功获得了同步状态。在释放同步状态时,同步器会调用unparkSuccessor()方法唤醒后继节点。3。可中断式获取锁
  可中断式获取锁(acquireInterruptibly方法)
  我们知道lock相较于synchronized有一些更方便的特性,比如能响应中断以及超时等待等特性,现在我们依旧采用通过学习源码的方式来看看能够响应中断是怎么实现的。可响应中断式锁可调用方法lock。lockInterruptibly();而该方法其底层会调用AQS的acquireInterruptibly方法,源码为:publicfinalvoidacquireInterruptibly(intarg)throwsInterruptedException{if(Thread。interrupted())thrownewInterruptedException();if(!tryAcquire(arg))线程获取锁失败doAcquireInterruptibly(arg);}
  在获取同步状态失败后就会调用doAcquireInterruptibly方法:privatevoiddoAcquireInterruptibly(intarg)throwsInterruptedException{将节点插入到同步队列中finalNodenodeaddWaiter(Node。EXCLUSIVE);booleanfailedtrue;try{for(;;){finalNodepnode。predecessor();获取锁出队if(pheadtryAcquire(arg)){setHead(node);p。nextnull;helpGCfailedfalse;return;}if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt())线程中断抛异常thrownewInterruptedException();}}finally{if(failed)cancelAcquire(node);}}
  关键信息请看注释,现在看这段代码就很轻松了吧,与acquire方法逻辑几乎一致,唯一的区别是当parkAndCheckInterrupt返回true时,即线程阻塞时该线程被中断,代码抛出被中断异常。4。超时等待式获取锁
  超时等待式获取锁(tryAcquireNanos()方法)
  通过调用lock。tryLock(timeout,TimeUnit)方式达到超时等待获取锁的效果,该方法会在三种情况下才会返回:在超时时间内,当前线程成功获取了锁;当前线程在超时时间内被中断;超时时间结束,仍未获得锁返回false。
  我们仍然通过采取阅读源码的方式来学习底层具体是怎么实现的,该方法会调用AQS的方法tryAcquireNanos(),源码为:publicfinalbooleantryAcquireNanos(intarg,longnanosTimeout)throwsInterruptedException{if(Thread。interrupted())thrownewInterruptedException();returntryAcquire(arg)实现超时等待的效果doAcquireNanos(arg,nanosTimeout);}
  很显然这段源码最终是靠doAcquireNanos方法实现超时等待的效果,该方法源码如下:privatebooleandoAcquireNanos(intarg,longnanosTimeout)throwsInterruptedException{if(nanosTimeout0L)returnfalse;1。根据超时时间和当前时间计算出截止时间finallongdeadlineSystem。nanoTime()nanosTimeout;finalNodenodeaddWaiter(Node。EXCLUSIVE);booleanfailedtrue;try{for(;;){finalNodepnode。predecessor();2。当前线程获得锁出队列if(pheadtryAcquire(arg)){setHead(node);p。nextnull;helpGCfailedfalse;returntrue;}3。1重新计算超时时间nanosTimeoutdeadlineSystem。nanoTime();3。2已经超时返回falseif(nanosTimeout0L)returnfalse;3。3线程阻塞等待if(shouldParkAfterFailedAcquire(p,node)nanosTimeoutspinForTimeoutThreshold)LockSupport。parkNanos(this,nanosTimeout);3。4线程被中断抛出被中断异常if(Thread。interrupted())thrownewInterruptedException();}}finally{if(failed)cancelAcquire(node);}}
  程序逻辑如图所示:
  程序逻辑同独占锁和响应中断式获取基本一致,唯一的不同在于获取锁失败后,对超时时间的处理上,在第1步会先计算出按照现在时间和超时时间计算出理论上的截止时间,比如当前时间是08:10,超时时间是10min,那么根据deadlineSystem。nanoTime()nanosTimeout计算出刚好达到超时时间时的系统时间就是08:1010min08:20。然后根据deadlineSystem。nanoTime()就可以判断是否已经超时了,比如,当前系统时间是08:20很明显已经超过了理论上的系统时间08:20,deadlineSystem。nanoTime()计算出来就是一个负数,自然而然会在3。2步中的If判断之间返回false。如果还没有超时即3。2步中的if判断为true时就会继续执行3。3步通过LockSupport。parkNanos使得当前线程阻塞,同时在3。4步增加了对中断的检测,若检测出被中断直接抛出被中断异常。5。公平锁和非公平锁
  还是看上面同步队列中的例子,以ReentrantLock的lock()方法为例。publicvoidlock(){sync。lock();}
  可以看一下这里的sync是有两个实现的:公平和非公平
  5。1公平锁
  进入AQS的acquire()方法,这个方法进去之后会调用AQS实现类的tryAcquire()方法。tryAcquire()方法分为公平锁和非公平锁的实现:
  下面看一下公平锁的实现(也就是FairSync的实现):staticfinalclassFairSyncextendsSync{。。。protectedfinalbooleantryAcquire(intacquires){获取当前线程finalThreadcurrentThread。currentThread();获取当前线程的状态,大于0则是获取到锁的(1代表获取一次锁,2代表重入了一次),等于0就是没获取到锁intcgetState();如果当前状态没获取锁if(c0){这里先判断当前队列是否为空,如果为空,当前队列就可以直接获取锁如果当前队列不为空,则后面直接返回false,让线程走上面分析的独占锁的获取流程if(!hasQueuedPredecessors()compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);returntrue;}}如果获取到锁的就是当前的线程,则直接重入一次,就是state1elseif(currentgetExclusiveOwnerThread()){intnextccacquires;if(nextc0)thrownewError(Maximumlockcountexceeded);setState(nextc);returntrue;}returnfalse;}}
  这里主要是!hasQueuedPredecessors()判断了当前队列是否为空,为空就加入队列尾部等待,所以称之为公平锁。5。2非公平锁
  可以看到非公平锁的实现,上来就直接尝试获取锁(就是compareAndSetState(0,1)),如果获取不到,就调用AQS的acquire()方法,这里进入AQS的acquire()方法之后,再看非公平锁的tryAcquire(arg)实现staticfinalclassNonfairSyncextendsSync{protectedfinalbooleantryAcquire(intacquires){returnnonfairTryAcquire(acquires);}}abstractstaticclassSyncextendsAbstractQueuedSynchronizer{。。。finalbooleannonfairTryAcquire(intacquires){获取当前线程finalThreadcurrentThread。currentThread();获取当前线程的状态,大于0则是获取到锁的(1代表获取一次锁,2代表重入了一次),等于0就是没获取到锁intcgetState();如果当前状态没获取锁if(c0){这里就不会像公平锁一样去判断队列是否为空了,直接回尝试抢占锁。如果抢到了就获取锁,没抢到才会加入队列尾部进行排队if(compareAndSetState(0,acquires)){setExclusiveOwnerThread(current);returntrue;}}如果获取到锁的就是当前的线程,则直接重入一次,就是state1elseif(currentgetExclusiveOwnerThread()){intnextccacquires;if(nextc0)overflowthrownewError(Maximumlockcountexceeded);setState(nextc);returntrue;}returnfalse;}四、共享锁
  共享锁就需要看一下ReentrantReadWriteLock类了,这个是一个读写锁,它的读锁就是共享锁,写锁就是互斥锁:读读共享、读写互斥,写写互斥。
  ReentrantReadWriteLock的API介绍ReentrantReadWriteLockreadWriteLocknewReentrantReadWriteLock();获取读锁ReentrantReadWriteLock。ReadLockreadLockreadWriteLock。readLock();readLock。lock();readLock。unlock();获取写锁ReentrantReadWriteLock。WriteLockwriteLockreadWriteLock。writeLock();writeLock。lock();writeLock。unlock();1。共享锁的获取
  共享锁的获取(acquireShared()方法)
  可以看看上面对于读写锁的API介绍,看看readLock。lock();和writeLock。lock();的源码可以发现:readLock。lock();调用的是AQS的acquireShared()方法,也就是获取共享锁writeLock。lock();调用的是AQS的acquire()方法,也就是互斥锁
  下面看一下共享锁是怎么获取的,直接上acquireShared()方法的源码:publicfinalvoidacquireShared(intarg){if(tryAcquireShared(arg)0)doAcquireShared(arg);}
  这段源码的逻辑很容易理解,在该方法中会首先调用tryAcquireShared方法,tryAcquireShared返回值是一个int类型,当返回值为大于等于0的时候方法结束说明获得成功获取锁,否则,表明获取同步状态失败即所引用的线程获取锁失败,会执行doAcquireShared方法,该方法的源码为:privatevoiddoAcquireShared(intarg){finalNodenodeaddWaiter(Node。SHARED);booleanfailedtrue;try{booleaninterruptedfalse;for(;;){finalNodepnode。predecessor();if(phead){intrtryAcquireShared(arg);if(r0){当该节点的前驱节点是头结点且成功获取同步状态setHeadAndPropagate(node,r);p。nextnull;helpGCif(interrupted)selfInterrupt();failedfalse;return;}}if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt())interruptedtrue;}}finally{if(failed)cancelAcquire(node);}}
  现在来看这段代码会不会很容易了?逻辑几乎和独占式锁的获取一模一样,这里的自旋过程中能够退出的条件是当前节点的前驱节点是头结点并且tryAcquireShared(arg)返回值大于等于0即能成功获得同步状态。
  这里以公平锁为例,看一下tryAcquireShared(arg)的源码:staticfinalclassFairSyncextendsSync{。。。protectedinttryAcquireShared(intacquires){for(;;){队列中有等待线程则不获取锁,返回1if(hasQueuedPredecessors())return1;获取状态intavailablegetState();intremainingavailableacquires;尝试设置状态,但是这里获取锁的时候并不会给state加一,因为是共享锁if(remaining0compareAndSetState(available,remaining))returnremaining;}}}
  这里和独占锁有一个很重要的区别,就是在独占锁中是调用了AQS的acquireQueued方法去排队等待获取锁,而共享锁只是将当前线程加入了队列中,然后调用tryAcquireShared(arg)尝试获取锁,并且共享锁并不会给state加1。
  而互斥锁,就是这里的写锁会给state加1,代表当前是有互斥锁的。
  总结互斥锁会给AQS的state从0改为1(代表当前线程获取到锁),如果重入锁,state会累加1,释放锁state就会累减1,减到0后就完全释放锁了共享锁并不会给AQS的state加一或减一,并且共享锁加入队列后并不会一直自旋等待获取锁,而是直接看state是否为0,为0代表没有写锁了,不为0就代表在它前面还有写锁。读写互斥2。共享锁的释放
  共享锁的释放(releaseShared()方法)
  共享锁的释放在AQS中会调用方法releaseShared:publicfinalbooleanreleaseShared(intarg){if(tryReleaseShared(arg)){doReleaseShared();returntrue;}returnfalse;}
  当成功释放同步状态之后即tryReleaseShared会继续执行doReleaseShared方法:privatevoiddoReleaseShared(){Ensurethatareleasepropagates,evenifthereareotherinprogressacquiresreleases。ThisproceedsintheusualwayoftryingtounparkSuccessorofheadifitneedssignal。Butifitdoesnot,statusissettoPROPAGATEtoensurethatuponrelease,propagationcontinues。Additionally,wemustloopincaseanewnodeisaddedwhilewearedoingthis。Also,unlikeotherusesofunparkSuccessor,weneedtoknowifCAStoresetstatusfails,ifsorechecking。for(;;){Nodehhead;if(h!nullh!tail){intwsh。waitStatus;if(wsNode。SIGNAL){if(!compareAndSetWaitStatus(h,Node。SIGNAL,0))continue;looptorecheckcasesunparkSuccessor(h);}elseif(ws0!compareAndSetWaitStatus(h,0,Node。PROPAGATE))continue;looponfailedCAS}if(hhead)loopifheadchangedbreak;}}
  这段方法跟独占式锁释放过程有点不同,在共享式锁的释放过程中,对于能够支持多个线程同时访问的并发组件,必须保证多个线程能够安全的释放同步状态,这里采用的CAS保证,当CAS操作失败continue,在下一次循环中进行重试。3。可中断和超时等待
  可中断(acquireSharedInterruptibly()方法),超时等待(tryAcquireSharedNanos()方法)
  关于可中断锁以及超时等待的特性其实现和独占式锁可中断获取锁以及超时等待的实现几乎一致,具体的就不再说了,如果理解了上面的内容对这部分的理解也是水到渠成的。五、锁
  锁升级和锁降级不是一回事,锁升级是synchronized关键字在jdk1。6之后做的优化,锁降级是为了保证数据的可见性在添加了写锁后再添加一道读锁1。锁升级
  锁升级的顺序为:无锁偏向锁轻量级锁重量级锁,且锁升级的顺序是不可逆的。
  因为操作唤醒阻塞的线程需从用户态切换到内存态,开销大,所以才优先使用轻量级锁进行循环判断是否可获取监视类或对象。
  线程第一次获取锁获时锁的状态为偏向锁,如果下次还是这个线程获取锁,则锁的状态不变,否则会升级为CAS轻量级锁;如果还有线程竞争获取锁,如果线程获取到了轻量级锁没啥事了,如果没获取到会自旋,自旋期间获取到了锁没啥事,超过了10次还没获取到锁,锁就升级为重量级的锁,此时如果其他线程没获取到重量级锁,就会被阻塞等待唤起,此时效率就低了。2。锁降级
  锁降级:写锁降级成为读锁。持有写锁的同时,获取到读锁,然后释放写锁。避免读到被其他线程修改的数据。
  oracle官网锁降级示例:classCachedData{Objectdata;volatilebooleancacheValid;finalReentrantReadWriteLockrwlnewReentrantReadWriteLock();voidprocessCachedData(){rwl。readLock()。lock();缓存无效if(!cacheValid){Mustreleasereadlockbeforeacquiringwritelock释放读锁rwl。readLock()。unlock();尝试获取写锁rwl。writeLock()。lock();try{Recheckstatebecauseanotherthreadmighthaveacquiredwritelockandchangedstatebeforewedid。再次判断获取是否无效if(!cacheValid){获取数据data。。。cacheValidtrue;}Downgradebyacquiringreadlockbeforereleasingwritelock锁降级rwl。readLock()。lock();}finally{rwl。writeLock()。unlock();Unlockwrite,stillholdread}}经过很长的时间做一些处理、而且不想我刚刚自己更新的数据被别人改了try{use(data);}finally{rwl。readLock()。unlock();}}}六、Synchronized和Lock区别
  百度抄一把!!!
  存在层面:
  synchronized:是Java中的一个关键字,存在于JVM层面
  Lock:是Java中的一个接口
  锁的释放条件:
  synchronized:1、以获取锁的线程执行完同步代码,释放锁2、线程执行发生异常,jvm会让线程释放锁
  Lock:在finally中必须释放锁,不然容易造成线程死锁
  锁的获取:
  synchronized:在发生异常时候会自动释放占有的锁,因此不会出现死锁
  Lock:发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生
  锁的状态:
  synchronized:无法判断
  Lock:可以判断
  锁的类型:
  synchronized:可重入不可中断非公平
  Lock:可重入可判断可公平(两者皆可)
  性能:
  synchronized:少量同步
  Lock:大量同步Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
  调度:
  synchronized:使用Object对象本身的wait、notify、notifyAll调度机制
  Lock:可以使用Condition进行线程之间的调度
  用法:
  synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
  Lock:一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
  底层实现:
  synchronized:底层使用指令码方式来控制锁的,映射成字节码指令就是增加来两个指令:monitorenter和monitorexit。当线程执行遇到monitorenter指令时会尝试获取内置锁,如果获取锁则锁计数器1,如果没有获取锁则阻塞;当遇到monitorexit指令时锁计数器1,如果计数器为0则释放锁。
  Lock:底层是CAS乐观锁,依赖AbstractQueuedSynchronizer类,把所有的请求线程构成一个CLH队列。而对该队列的操作均通过LockFree(CAS)操作。

异度之刃3别针销获取指南,阿提亚地区地标,有哪些饲料选择异度之刃是JRPG中最好的系列之一,凭借广阔的开放地图、充满战略深度的指令战斗、波澜壮阔的剧情以及高品质的音乐赢得了广大玩家的好评。在玩的过程中,可能也有同伴会问这样的问题。如……打破土地的限制,发展共享农业,共享农业迎来新的爆发点?所谓共享农业,就是利用互联网等现代信息技术整合分享涉农资源,满足多样化需求的现代农业经济活动的总和。发展共享农业的条件已经成熟,可以说共享农业即将迎来新的爆发点。从技术上……一首歌吃一辈子的歌手现状有人暴富,有人离婚,有人成网红对于一名歌手而言,最重要的就是有代表作品,因为这样观众才不会忘记自己。而有些歌手一辈子只有那么一首经典作品,但是却借此红遍了大江南北,还赚到了惊人的财富。然而,随着……几何华为中国智造正在原力觉醒几何汽车正式牵手华为,将在旗下新车型G6M6搭载鸿蒙系统,共创全新智慧生态,这意味着几何G6M6成为20万内唯一搭载鸿蒙系统的纯电车型。华为鸿蒙智能座舱的导入将给消费者带来更具……与去年上半年相比,俄罗斯廉价智能手机出货量减少了56据《生意人报》8月17日报道,俄罗斯投资和工业公司GS集团近日发布了2022年上半年俄罗斯联邦手机进口情况的数据。数据显示,价格在1万卢布的智能手机的供应量,与去年同期相比减少……在这寂静的夜里在寂静的夜里,我坐在明亮的房间里,倾听着窗外的声音,窗外响起了亲切而又懒散的声音,这声音搅乱了沉沉长夜的无言的寂静。璀璨的群星在漆黑的夜空中闪烁,它们彼此间离得这么近,却没有任……你对现在的你满意吗?时光荏苒,岁月如梭,素什锦年,稍纵即逝,半载青春年华,似沙漏般,弹指间,流在昨天。理想总是美好的,现实总是残酷的。我们每个人小时候都有着对自己长大的憧憬,回首看来,现在的……国民下饭神器老干妈的衰败之路死去的回忆突然出现,你有多久没有用老干妈下饭了?老干妈冲上热搜,这次是为何此前在贵州省工商业联合会与贵州省企业联合会共同发布2022贵州民营企业100强榜单中,老干……电信联通一张网的逻辑01移动通信运营商的核心资源移动通信运营商运营的核心资源就是无线电频段资源,而资源的禀赋直接决定了运营商是先天条件是好还是坏。无论无线网技术如何发展,耕作的都是这些频率资……荣耀80系列参数曝光,搭载骁龙8处理器2亿像素荣耀近期的动作很大,近几个月接连发布了荣耀X40和荣耀X40GT,又在官网无声息的上新了一款荣耀Play6C。荣耀作为一个偏向年轻化的手机品牌,性价比偏高,价格很是友好,并且作……主力资金连续4日净买入名单曝光更新日期:10月22日若不喜欢看表格,输入任何数字进入底部图片区域。连续抢筹主力资金连续多日净买入名单序号代码名称主力连买天数主……生活中有哪些食物有可能引发肠癌,你知道吗?大家好,我是中医博士王佰庆。今天聊一聊生活中有哪些食物有可能引发肠癌。第一种,熏烤、油炸类的肉类。很多人对烧烤、烤肉情有独钟,尤其是在烤肉的时候经常会把肉边给烤糊了,其实……
看一次笑一次的穿帮镜头,古代竟然有耳钉,吹笛子的真熟练看一次笑一次的穿帮镜头,古代竟然有耳钉,吹笛子的真熟练这部古装剧中,我们可以清楚地看见男演员耳朵上竟然有打的耳洞,所以小编就想,难道在古代的时候就已经流行打耳洞了吗?……男篮亚洲杯,淘汰赛对阵情况出炉,中国男篮避开了2最大争冠热门2022年男篮亚洲杯小组赛,已经结束了全部比赛,中国男篮在小组赛的表现,可以说是不如人意,只取得了2胜1负的成绩,只以小组第二名的成绩晋级淘汰赛。在首轮比赛中国男篮输给了韩国队……10月20日直播预告,国乒王曼昱樊振东出赛,张本智和值得关注澳门冠军赛次日,王曼昱战老将袁佳楠,樊振东遇同胞黄镇廷WTT澳门冠军赛2022,10月20日16场赛程如下;咪咕继续直播。国乒王曼昱对阵法国一姐袁嘉楠,樊振东遇上中国香港……1688露营装备源头工厂合集,100内就能入手一套天幕暑假到了,往年晒海岛游、欧洲游、自驾游的朋友们,今年纷纷晒起了露营!很长一段时间以来,各种露营活动火爆全网,装备的价格也水涨船高。今年帐篷天幕的价格,基本就是翻倍还不止了,一度……女排又传好消息,封闭备战世锦赛,蔡斌再次接受采访,透露新消息最近随着女排世联赛比赛结束,中国女排的姑娘们也回归祖国的怀抱,开始了接下来几个大比赛的备战,据了解,女排姑娘们并没有怎么休息,而是一回归就开始在北仑集训中心封闭集训。毕竟时间紧……水花茶泡枸杞?勇士胜掘金夺赛点,127的他却成了马龙最惧之人北京时间4月22日,NBA迎来了一场焦点战。金州勇士队前往客场挑战丹佛掘金队,勇士队首发是普洱,克莱,枸杞哥,格林,卢尼,库里继续替补出场。本场比赛虽说是掘金的主场,但是也正如……除杨过外,小龙女还对一人动过情,此人实力吊打杨过,还文武双全杨过跟小龙女的恋情算千古绝恋吗?放到金庸的武侠世界,大概是算的,毕竟金庸武侠感情戏是比较少的,大多讲侠义之道,爱情只是用来润色的。情感戏方面,神雕侠侣显得尤为特别,整个剧情都是……发到朋友圈让男人心疼到心碎的话1。希望那些美好的人和事像回旋镖一样,明明跑远了,可过一阵儿又自己绕回来。比如风吹了八百里,最后还落在我心里。2。一段感情最难过的,不是恶语相加,也不是冷漠疏离,而是曾经……凯特王妃带儿子看球赛却被骂?穿西装高温暴晒网友他要热疯了这几天,英国最大的事情就是温网了。(温布尔登网球锦标赛,历史最悠久、最具声望的网球公开赛事,四大满贯之一)除了精彩的比赛之外,温网最被人津津乐道的就是星光璀璨的看台……盘一盘晕头转向的RunnableCallableFuture今天我们来一起盘一盘应用场景很多的实现类,虽然我们不经常直接使用这些类,但是在各种地方都有它们的身影(比如线程池),且它们很容易搞混,我称之为可知结果的未来任务FutureTa……KPOP文化实在看不惯?外国人讨厌韩国的8个理由在Kpop兴起的这段日子,去韩国旅游的人越来越多,也不少人纷纷从哈日转变为哈韩,成为爱好韩国文化的一份子,但是在很多外国人的眼裡看来,韩国却是没有那麽吸引他们的地方,你们知道是……新晋排球女神网红范十足,带妆上场引网友热议,与19岁男友姐弟新晋排球女神网红范十足,带妆上场引网友热议,与19岁男友姐弟恋。女排近年来一直是网友热议的话题,而在女排的队伍当中,也更是人才辈出,不管是朱婷还是张常宁,他们都为这个团队做出了……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网