独占锁模式 前面说到,ReentrantLock锁就是基于独占锁实现的,独占锁的加锁和解锁操作都是通过互斥方式实现的。 加锁流程 在AQS中,是通过acquire()方法来加锁的,源码如下:publicfinalvoidacquire(intarg){if(!tryAcquire(arg)acquireQueued(addWaiter(Node。EXCLUSIVE),arg))selfInterrupt();}复制代码 首先映入眼帘的是tryAcquire(arg)方法,翻译过来是尝试获取锁的意思,由于是用连接,只有tryAcquire(arg)方法失败时才会往下执行,当tryAcquire(arg)方法成功返回true时表示获取资源成功,对于tryAcquire(arg)方法,里面没有实现具体的东西,只是抛出了一个异常,具体逻辑是由AQS的子类实现的:protectedbooleantryAcquire(intarg){thrownewUnsupportedOperationException();}复制代码 当tryAcquire(arg)返回为false时,会往后执行addWaiter(Node。EXCLUSIVE)方法,将当前线程封装成独占模式并添加到AQS的等待队列尾部,如果当tryAcquire(arg)返回true,则会直接让当前的线程继续执行,不需要添加到等待队列尾部,点入addWaiter(Nodemode)方法,会看到如下源码:privateNodeaddWaiter(Nodemode){这个可以参考上面Node的构造方法Node(Threadthread,Nodemode){UsedbyaddWaiterthis。nextWthis。}构造新的等待线程节点NodenodenewNode(Thread。currentThread(),mode);新建临时节点pred指向尾节点N队列不为空的话,通过CAS机制将node放到队列尾部if(pred!null){将node的prev域指向尾节点node。通过CAS机制将node放到队列尾部if(compareAndSetTail(pred,node)){将原来尾节点的next域指向当前node节点,node现在为尾节点pred。形成双向链表}}如果队列为空的话enq(node);}复制代码 在多线程并发情况下,如果有多个线程同时争夺尾节点的位置,会调用enq(node)方法,使用CAS自旋机制挂到双向链表的尾部,下面是源码:privateNodeenq(finalNodenode){死循环(自旋)for(;;){N尾节点为null,说明头结点也为null,可能是还没有创建队列的时候if(tnull){多线程并发情况下,利用CAS机制创建头结点和尾节点,CAS保证此时只有一个头节点被创建,下次自旋时,就会满足队列不为空的条件if(compareAndSetHead(newNode()))}else{如果存在尾节点,将当前节点的prev域指向尾节点node。利用CAS机制完成双向链表的绑定,让之前尾节点指向当前node节点if(compareAndSetTail(t,node)){t。}}}}复制代码 接下来看一看compareAndSetTail方法使用CAS乐观锁机制的方法源码:privatefinalbooleancompareAndSetTail(Nodeexpect,Nodeupdate){returnunsafe。compareAndSwapObject(this,tailOffset,expect,update);}复制代码 上述代码讨论到,使用tryAcquire(intarg)方法返回false时,再使用addWaiter(Nodemode)方法,将当前线程放入到队列的尾部。acquireQueued(finalNodenode,intarg)方法,等待休息直到其他线程唤醒finalbooleanacquireQueued(finalNodenode,intarg){拿到资源失败情况置为true,表示没有拿到资源try{用于判断线程是否中断过,默认没有中断过for(;;){获取node的前置节点finalNodepnode。predecessor();如果当前节点的前驱节点是头结点,并且当前节点尝试成功获取了资源if(pheadtryAcquire(arg)){就将当前节点设为头结点setHead(node);释放之前的头结点,利于垃圾回收p。helpGC表示获取资源成功返回线程是否被中断过}获取资源失败后线程等待,并检查是否被中断过if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt())线程是否被中断过}}finally{if(failed)cancelAcquire(node);}}复制代码 如果当前的前驱节点表示头结点,并且获取资源成功,那么直接将当前线程设为头结点,释放之前头结点与后继节点的链接,帮助垃圾回收(GC),如果前面当前节点的前驱不为头结点或者没有获取到资源,那么会调用shouldParkAfterFailedAcquire(Nodepred,Nodenode)方法来判断当前线程是否能够进入waiting状态,如果可以进入,并且进入到了阻塞状态,那会阻塞,直到调用了LockSupport中的unpark()方法唤醒线程。privatestaticbooleanshouldParkAfterFailedAcquire(Nodepred,Nodenode){保存前驱节点的状态提示:当waitState0时,表示该线程处于取消状态(线程中断或者等待锁超时),需要移除线程;当waitState0时,默认值,表示初始化状态,表示线程还未完成初始化操作;当waitState0,表示有效状态,线程处于可唤醒状态。intwspred。waitS等待唤醒后置节点,SIGNAL为1if(wsNode。SIGNAL)如果前置节点不是正常的等待状态(CANCELLED结束状态),那么从当前节点开始往前寻找正常的等待状态if(ws0){do{后面的节点断开与前驱节点的链接node。prevpredpred。}while(pred。waitStatus0);双向连接pred。}else{小于0时,可能为共享锁compareAndSetWaitStatus(pred,ws,Node。SIGNAL);}}复制代码 如果前驱节点的SIGNAL值为1,会返回true。 compareAndSetWaitStatus(pred,ws,Node。SIGNAL)方法内部也使用了CAS锁机制,源码:privatestaticfinalbooleancompareAndSetWaitStatus(Nodenode,intexpect,intupdate){returnunsafe。compareAndSwapInt(node,waitStatusOffset,expect,update);}复制代码 如果shouldParkAfterFailedAcquire(Nodepred,Nodenode)方法返回true,则会调用parkAndCheckInterrupt()方法阻塞当前线程,线程等待,如果线程被中断过则返回true:privatefinalbooleanparkAndCheckInterrupt(){调用park让线程进入wait状态LockSupport。park(this);检查线程是否中断过。returnThread。interrupted();}复制代码 如果线程在等待的过程中被中断过,那么获取到资源后会通知线程中断:Conveniencemethodtointerruptcurrentthread。staticvoidselfInterrupt(){Thread。currentThread()。interrupt();}复制代码 acquire()竞争获取锁资源流程时,首先会调用tryAcquire()方法去尝试获取资源,如果成功获取到资源,则直接进入临界区执行代码;如果没有获取到资源,则将此线程封装成一个结点放入队列尾部分,调用park()方法让线程等待,并且标记为独占模式。如果线程被唤醒(unPark)时,会尝试获取锁资源,如果在等待过程中,线程被中断过则返回true,没有被中断过返回false。如果线程在等待过程中被中断过,它是不会响应,只是在获取到资源后直接调用自我中断方法selfInterrupt(),将中断线程。 释放独占锁 在独占锁中,释放锁的入口是release()方法,源码如下:publicfinalbooleanrelease(longarg){if(tryRelease(arg)){Nif(h!nullh。waitStatus!0)唤醒后继节点unparkSuccessor(h);}}复制代码 点入tryRelease(arg)方法尝试释放锁,可以看出,其和tryAcquire()方法一样,也没有具体的实现,只是抛出了UnsupportedOperationException()异常,具体的逻辑由AQS的子类实现:protectedbooleantryRelease(longarg){thrownewUnsupportedOperationException();}复制代码 如果tryRelease(longarg)方法返回true,会判断头结点是否为空,并且waitStatus是否为0(为0则代码初始化阶段),如果不为0,那么下面会调用unparkSuccessor()方法,唤醒后继节点,我们来查看unparkSuccessor()方法的源码:privatevoidunparkSuccessor(Nodenode){intwsnode。waitS如果当前状态为有效状态if(ws0)CAS操作将waitState置为0compareAndSetWaitStatus(node,ws,0);Nodesnode。下一个节点(线程)为空或已取消if(snulls。waitStatus0){从后往前遍历,寻找有效的节点for(Nt!nullt!tt。prev)if(t。waitStatus0)}if(s!null)找到则唤醒后继节点LockSupport。unpark(s。thread);}复制代码 unparkSuccessor(Nodenode)方法实现的是唤醒后继节点,当当前节点的waitStatus状态小于0时,表示该状态为有效状态,会使用CAS机制将当前线程设为初始化状态0,之后找到下一个需要唤醒的节点,如果下一个需要唤醒的节点为空或者为取消状态则将当前线程置为null,之后从尾节点往前遍历,寻找有效的节点,找到了且不为null的话,就唤醒该节点(线程)。共享模式加锁 在共享模式下加锁的方法入口为acquireShared(longarg)方法,其源码如下:publicfinalvoidacquireShared(longarg){if(tryAcquireShared(arg)0)doAcquireShared(arg);}复制代码 进入到tryAcquireShared(arg)方法,此方法为尝试获取资源,得到如下源码,与独占模式获取锁一样,tryAcquireShared(longarg)没有实现具体的逻辑,由AQS的子类实现:protectedlongtryAcquireShared(longarg){thrownewUnsupportedOperationException();}复制代码 tryAcquireShared(arg)会有三种返回值:当返回值为负数时,表示获取资源失败;当返回值为0时,表示获取资源成功,没有剩余资源;当返回值为正数时,表示当前线程获取到了资源,仍然有资源剩余。 当tryAcquireShared(arg)方法返回为false时,表示获取资源失败,会往下进行doAcquireShared(arg)方法,此方法会将线程放入到等待队列尾部休息,点入doAcquireShared(arg)方法的源码得:privatevoiddoAcquireShared(longarg){将节点加入到队列尾部finalNodenodeaddWaiter(Node。SHARED);获取资源成功的标志,初始为获取不到try{中断的标志自旋for(;;){获取当前节点的前驱节点finalNodepnode。predecessor();如果前驱节点是头节点的话if(phead){返回还剩下多少资源longrtryAcquireShared(arg);if(r0){如果资源还足够的话,将头结点指向自己,如果还有剩余资源,可以唤醒后面的节点setHeadAndPropagate(node,r);断开之前的头结点,便于垃圾回收p。helpGCif(interrupted)selfInterrupt();}}if(shouldParkAfterFailedAcquire(p,node)parkAndCheckInterrupt())}}finally{if(failed)cancelAcquire(node);}}复制代码 在共享模式下,当线程被唤醒拿到资源时,如果还有剩余资源,会继续唤醒后继的线程。如果被唤醒的线程发现资源不够用时会再次进入休眠。这个情况下,就算排在首位线程后面的线程需要更少的资源,也会因为前面资源不够而等待,不会先执行后面的线程。 对于上面代码中出现的setHeadAndPropagate()方法,点入查看得到以下源码:privatevoidsetHeadAndPropagate(Nodenode,longpropagate){将头结点赋值为hNRecordoldheadforcheckbelow将当前节点设置为头结点setHead(node);如果还有剩余资源的话,会唤醒后面的节点(线程)if(propagate0hnullh。waitStatus0(hhead)nullh。waitStatus0){Nodesnode。if(snulls。isShared())唤醒后继节点doReleaseShared();}}复制代码 释放共享资源 释放共享资源方法为doReleaseShared()privatevoiddoReleaseShared(){自旋for(;;){N头结点不为空并且头结点不等于尾节点if(h!nullh!tail){拿到头结点的等待状态intwsh。waitS如果当前节点为等待唤醒的节点if(wsNode。SIGNAL){将当前节点等待状态初始化,来唤醒线程if(!compareAndSetWaitStatus(h,Node。SIGNAL,0))looptorecheckcasesunparkSuccessor(h);唤醒后继线程}如果线程处于初始化状态,并且还有剩余资源elseif(ws0!compareAndSetWaitStatus(h,0,Node。PROPAGATE))looponfailedCAS}如果没有后继节点,退出自旋if(hhead)}}复制代码 在doReleaseShared()方法中,通过自旋的方式获取头节点,当头节点不为空,且队列不为空时,判断头节点的waitStatus状态的值是否为SIGNAL(1)。当满足条件时,会通过CAS将头节点的waitStatus状态值设置为0,如果CAS操作设置失败,则继续自旋。如果CAS操作设置成功,则唤醒队列中的后继节点。 如果头节点的waitStatus状态值为0,并且在通过CAS操作将头节点的waitStatus状态设置为PROPAGATE(3)时失败,则继续自旋逻辑。 如果在自旋的过程中发现没有后继节点了,则退出自旋逻辑。 本篇文章就分享到这里了,后续将会分享各种其他关于并发编程的知识,感谢大佬认真读完支持咯 文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起讨论希望能和诸佬们一起努力,今后进入到心仪的公司再次感谢各位小伙伴儿们的支持 作者:小威要向诸佬学习呀 链接:https:juejin。cnpost7164692725332181022