我们常见的并发锁ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier都是基于AQS实现的,所以如果不了解AQS的原理实现,你不能说你懂Java锁。上一篇文章讲了AQS的加锁过程。本文就来看看AQS具体的源码实现。先回顾一下AQS加锁过程1.AQS加锁过程AQS加锁过程并不复杂。只要了解了同步队列和条件队列,以及它们之间的数据流转,就可以完全理解AQS。多个线程竞争AQS锁时,如果有线程获取到锁,则设置owner线程为不竞争锁的线程,阻塞在同步队列中(同步队列采用双向链表和尾插入方式).持有锁的线程调用await方法,释放锁,追加到条件队列的尾部(条件队列采用单链表和尾插入方式)。持有锁的线程调用signal方法,唤醒条件队列的头节点,转移到同步队列的尾部。同步队列的头节点先获取锁,了解AQS加锁流程后,看源码就很容易理解了。2.AQS的数据结构//继承自AbstractOwnableSynchronizer,目的是为了记录哪个线程占用了锁publicabstractclassAbstractQueuedSynchronizerextendsAbstractOwnableSynchronizer{//同步状态,0表示没有锁,每次锁+1,释放锁-1privatevolatile内部状态;//同步队列头尾节点privatetransientvolatileNodehead;privatetransientvolatile节点尾部;//Node节点,用于包装线程并放入队列中staticfinalclassNode{//节点中的线程volatileThreadthread;//节点状态volatileintwaitStatus;//同步队列的前驱节点和后继节点volatileNodeprev;接下来是可变节点;//条件队列的后继节点NodenextWaiter;}//条件队列publicclassConditionObjectimplementsCondition{//条件队列的头节点和尾节点privatetransientNodefirstWaiter;私有瞬态节点lastWaiter;}}首先,AQS继承自AbstractOwnableSynchronizer,其实就是记录哪个线程占用了锁。公共抽象类AbstractOwnableSynchronizer{privatetransientThreadexclusiveOwnerThread;//设置占用锁的线程protectedfinalvoidsetExclusiveOwnerThread(Threadthread){exclusiveOwnerThread=thread;所有的线程都需要打包成Node节点。虽然同步队列和条件队列都是由Node节点组成,但是同步队列使用prev和next构成双向链表,nextWaiter只是用来表示是共享模式还是独占模式。条件队列没有使用Node中的prev和next属性,而是使用nextWaiter构成单向链表。这个复用对象的设计思路值得学习。同步队列头节点是哑节点,里面没有存放线程对象。当然,头节点也可以被认为是当前持有锁的线程正在使用。Node节点的状态(waitStatus)有5种:1canceled:表示该线程已经被取消0Initialization:Node节点的默认值-1signal:表示该节点线程将唤醒下一个节点线程inthesynchronizationqueueafterthereleaselock-2condition:当前节点在conditionqueue-3propagate:释放共享资源时,会向后传播释放其他共享节点(共享模式下使用)3.AQS方法概述AQS支持独占和共享两种访问资源的模式(独占模式也称为独占模式)。独占模式的方法://lockacquire();//加一个可中断的锁acquireInterruptibly();//一段时间内,如果加锁不成功,则不加tryAcquireNanos(intarg,longnanosTimeout);//释放锁release();共享模式方法://lockacquireShared();//加一个可中断的锁acquireSharedInterruptibly();//一段时间内,如果加锁不成功,则不加tryAcquireSharedNanos(intarg,longnanosTimeout);//释放锁releaseShared();独占模式和共享模式的方法并没有实现具体的加锁和释放逻辑,AQS只是定义了加锁和释放的抽象方法。留给子类实现的抽象方法://添加独占锁protectedbooleantryAcquire(intarg){thrownewUnsupportedOperationException();}//释放独占锁protectedbooleantryRelease(intarg){thrownewUnsupportedOperationException();}//添加sharedlockprotectedinttryAcquireShared(intarg){thrownewUnsupportedOperationException();}//释放共享锁protectedbooleantryReleaseShared(intarg){thrownewUnsupportedOperationException();}//判断当前线程是否持有锁protectedbooleanisHeldExclusively(){thrownewUnsupportedOperationException();}这里用到了设计模式中的模板模式。父类AQS定义了加锁和释放锁的过程,子类ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier负责实现具体的Lock和释放锁逻辑。这不是面试知识点吗?面试官又会问你,你见过哪些使用设计模式的框架源码?你可以回答说AQS源码中使用了模板模式,blahblahblah,妥妥的加分项!4.AQS源码分析整个加锁过程如下:先看加锁方法源码:4.1Locking//加锁方法,参数为1publicfinalvoidacquire(intarg){//1.首先尝试获取锁,如果获取成功,则设置state+1,exclusiveOwnerThread=currentThread(子类实现)if(!tryAcquire(arg)&&//2.如果获取不成功,将线程组装成Nodenode并将其追加到同步队列的末尾}}再看addWaiter方法的源码,作用是将线程组装成一个Node节点,追加到队列的同步端。//追加到同步队列的尾部,参数为共享模式或独占模式privateNodeaddWaiter(Nodemode){//1.组装成一个Node节点Nodenode=newNode(Thread.currentThread(),mode);节点pred=tail;if(pred!=null){node.prev=pred;//2.当多线程竞争不激烈时,通过CAS方法追加到同步队列尾部if(compareAndSetTail(pred,node)){pred.下一个=节点;返回节点;}}//3.在多线程竞争激烈的情况下,使用死循环保证追加到同步队列的尾部enq(node);returnnode;}//创建一个Node节点,参数为线程,共享模式或独占模式Node(Threadthread,Nodemode){this.thread=thread;this.nextWaiter=mode;}//通过无限循环追加到同步队列的末尾privateNodeenq(finalNodenode){for(;;){Nodet=tail;if(t==null){if(compareAndSetHead(newNode()))tail=head;}else{node.prev=t;if(compareAndSetTail(t,node)){t.next=node;返回吨;}}}}看一下addWaiter方法外层的acquireQueued方法。作用是:追加到同步队列尾部后,判断前驱如果是头节点,说明它最先加入同步队列,然后尝试获取锁。如果成功获取到锁,则将自己设置为头节点。如果前驱节点不是头节点,或者获取锁失败,则逆序遍历同步队列,寻找可以唤醒自己的节点。最后,自信的把自己挂了//追加到同步队列尾部后,再次尝试获取锁finalbooleanacquireQueued(finalNodenode,intarg){booleanfailed=true;尝试{布尔中断=false;for(;;){//1.找到前驱节点finalNodep=node.predecessor();//2.如果前驱节点是头节点,则再次尝试获取锁if(p==head&&tryAcquire(arg)){//3.成功获取锁后,将自己设为头节点setHead(节点);p.next=null;失败=假;返回中断;}//4.如果仍然没有获取到锁,寻找一个可以唤醒自己的节点}}finally{if(failed)cancelAcquire(node);}}再看看shouldParkAfterFailedAcquire方法,怎么找到自己唤醒的节点呢?你为什么要找这个节点?//加入同步队列后,寻找可以唤醒自己的节点privatestaticbooleanshouldParkAfterFailedAcquire(Nodepred,节点node){intws=pred.waitStatus;//1.如果前驱节点的状态已经是SIGNAL(释放锁后需要唤醒后继节点),则无需操作if(ws==Node.SIGNAL)returntrue;//2.如果前驱节点的状态为取消,则继续向前遍历if(ws>0){do{node.prev=pred=pred.prev;}while(pred.waitStatus>0);pred.next=节点;}else{//3.找到一个没有取消的节点,将节点状态设置为SIGNALcompareAndSetWaitStatus(pred,ws,Node.SIGNAL);}returnfalse;}从代码中可以清楚的看出,目的是找到一个不在cancel状态的节点,将该节点的状态设置为SIGNAL状态为SIGNAL的节点。释放锁后,需要唤醒其后继节点。简单的理解就是:小弟刚来,特地来通知老板,有什么好东西,请多告诉小弟。再看一下释放锁的逻辑。4.2释放锁释放锁的过程如下:释放锁的代码逻辑比较简单://释放锁publicfinalbooleanrelease(intarg){//1.首先尝试释放锁,如果成功,则设置state-1,exclusiveOwnerThread=null(由子类实现)if(tryRelease(arg)){Nodeh=head;//2.如果同步队列中还有其他节点,则唤醒下一个节点if(h!=null&&h.waitStatus!=0)//3.唤醒后继节点unparkSuccessor(h);返回真;}returnfalse;}再看唤醒后继节点的方法//唤醒后继节点privatevoidunparkSuccessor(Nodenode){intws=node.等待状态;//1.如果头节点没有取消,则重置为初始状态if(ws<0)compareAndSetWaitStatus(node,ws,0);节点s=node.next;//2.如果后继节点为null或者取消状态if(s==null||s.waitStatus>0){s=null;//3.从队尾遍历寻找有效节点for(Nodet=tail;t!=null&&t!=node;t=t.prev)if(t.waitStatus<=0)s=吨;}//3.唤醒这个有效节点if(s!=null)LockSupport.unpark(s.thread);}4.3await等待await等待过程:持有锁的线程可以调用await方法,效果是:释放锁,并附加到条件队列的末尾//等待方法publicfinalvoidawait()throwsInterruptedException{//如果线程被中断,则中断if(Thread.interrupted())thrownewInterruptedException();//1.追加到条件队列的末尾Nodenode=addConditionWaiter();//2.释放锁intsavedState=fullyRelease(node);中断模式=0;//3.可能刚加入条件队列就转入同步队列。如果它仍然在条件队列中,您可以安全地挂起Start自己while(!isOnSyncQueue(node)){LockSupport.park(this);如果((interruptMode=checkInterruptWhileWaiting(节点))!=0)中断;}//4.如果已经转入同步队列,尝试获取锁if(acquireQueued(node,savedState)&&interruptMode!=THROW_IE)interruptMode=REINTERRUPT;if(node.nextWaiter!=null)//5.清除条件队列中取消的节点unlinkCancelledWaiters();if(interruptMode!=0)reportInterruptAfterWait(interruptMode);}再看看addConditionWaiter方法,它是怎么追加到条件队列尾部的?//附加到条件队列的末尾privateNodeaddConditionWaiter(){Nodet=lastWaiter;//1.清除取消的节点并找到有效节点if(t!=null&&am;t.waitStatus!=Node.CONDITION){unlinkCancelledWaiters();t=最后一个服务员;}//2.创建状态为-2的Node节点(表示在条件队列中)Nodenode=newNode(Thread.currentThread(),Node.CONDITION);//3.附加到条件队列的末尾if(t==null)firstWaiter=node;否则t.nextWaiter=节点;lastWaiter=节点;条件队列的头节点,追加到同步队列的尾部//唤醒条件队列的头节点publicfinalvoidsignal(){//1.只有持有锁的线程才能调用signal方法if(!isHeldExclusively())thrownewIllegalMonitorStateException();//2.找到条件队列头节点Nodefirst=firstWaiter;if(first!=null)//3.开始唤醒doSignal(first);}//实际唤醒方法privatevoiddoSignal(Nodefirst){do{//4.从条件队列中取出头节点if((firstWaiter=first.nextWaiter)==null)lastWaiter=null;first.nextWaiter=null;//5.使用无限循环,一定要转移一个节点到同步队列}while(!transferForSignal(first)&&(first=firstWaiter)!=null);}怎么转移到同步队列尾部的?//实际传输方法finalbooleantransferForSignal(Nodenode){//1.将节点状态从CONDITION更改为0if(!compareAndSetWaitStatus(node,Node.CONDITION,0))returnfalse;//2.使用无限循环的方法,追加到同步队列的末尾(如前所述)Nodep=enq(node);intws=p.waitStatus;//3.设置前驱节点状态为SIGNAL(通知他,别忘了叫醒小弟)if(ws>0||!compareAndSetWaitStatus(p,ws,Node.SIGNAL))LockSupport.unpark(node.thread);returntrue;}5.小结看完AQS的全部源码,你是否完全理解了AQS加锁、释放锁、同步队列和条件队列之间数据流转的逻辑?连AQS都这么复杂ReentrantLock的源码你已经搞清楚了,下一篇文章带你学习ReentrantLock的源码,应该会容易很多。
