一、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)操作。