JDK成长笔记20:ReenranctLock(三)AQS释放锁的底层原理
时间:2023-04-01 23:27:49
Java
前面两节,大家应该已经掌握了ReentrantLock加锁成功和加锁失败后入队的核心逻辑。如何通过AQS中的三个组件来实现的。今天来看看:ReentrantLock中,线程释放锁时,逻辑上释放锁的过程及源码分析大小:16px;颜色:rgb(62,62,62);行高:1.6;字间距:0px;字母间距:0px;字体系列:'HelveticaNeue',Helvetica,'HiraginoSansGB','MicrosoftYaHei',Arial,sans-serif;">释放锁的过程及源码分析
目前线程1和线程2使用ReentrantLock.lock()后的结果如下:线程2入队等待,线程1持有锁,state=1,owner为线程1,并且队列中的元素为线程2,队列是由Node节点组成的双向链表,头节点为空,队列的具体情况如下:假设线程1调用unlock()方法释放锁,它会做什么?首先,让我们看一下发布代码:publicvoidunlock(){sync.release(1);}很简单,还是用Sync组件(AQS)释放锁release()方法publicfinalbooleanrelease(intarg){if(tryRelease(arg)){Nodeh=head;如果(h!=null&&h.waitStatus!=0)unparkSuccessor(h);返回真;}returnfalse;}核心分为2步:1)tryRelease方法,释放state和owner变量2)UnparkSuccessor方法,唤醒队列元素分开看,先释放变量tryRelease方法:protectedfinalbooleantryRelease(intreleases){intc=getState()-releases;if(Thread.currentThread()!=getExclusiveOwnerThread())thrownewIllegalMonitorStateException();布尔自由=假;如果(c==0){免费=真;setExclusiveOwnerThread(null);}设置状态(c);该线程为线程1,state=1,入参releases为1,表示释放一次锁。显然,state减1变为0后,owner也会被置空,说明当前没有线程持有锁,然后state置0结束。整个过程如下图所示:修改好两个组件owner和state的值后,第二步就是唤醒队列中等待的线程。publicfinalbooleanrelease(intarg){if(tryRelease(arg)){节点h=head;如果(h!=null&&h.waitStatus!=0)unparkSuccessor(h);返回真;}returnfalse;}first释放锁成功后,使用h指针指向当前队列的头部,判断队列中是否有等待元素。注意头元素的waitStatus不能为0,如果为0,说明队列只有一个空节点,队列中没有等待元素。元素。因为进入队列后,头节点的waitStatus会变成-1,SIGNAL。然后进入unpartSuccessor方法。从名字上看,就是恢复h节点后挂起的线程。(PS:JDK源码中总会有一些语义相近的词,比如first-last,head-tail,successor-predecessor,prev-next,其实都是前后的意思,是等价的。这个大家应该都清楚了。)privatevoidunparkSuccessor(Nodenode){intws=node.waitStatus;如果(ws<0)compareAndSetWaitStatus(节点,ws,0);节点s=node.next;如果(s==null||s.waitStatus>0){s=null;for(Nodet=tail;t!=null&&t!=node;t=t.prev)if(t.waitStatus<=0)s=t;}if(s!=null)LockSupport.unpark(s.thread);}node为入参h,头节点,首先将头节点的waitStatus由-1改为0。那么s=node.next表示线程2排队的Node,s不为空,LockSupport.unpark(s.thread);执行唤醒线程2,最后整个方法返回,release返回true表示释放锁成功。如下图所示:这段时间之前被挂起的线程2,还记得吗?即线程2已经执行了LockSupport.park(t)操作,线程被挂起,代码会挂在那一行等待。privatefinalbooleanparkAndCheckInterrupt(){LockSupport.park(this);returnThread.interrupted();}线程2会继续执行,只要线程2没有被中断,return一定是false,后面的if条件不会成立。if(shouldParkAfterFailedAcquire(p,node)&&parkAndCheckInterrupt())然后回到for循环,重新做tryAcquire操作,线程2再次尝试获取锁,假设没有其他线程过来获取锁。线程2自然会获取到锁,设置state=1,owner=线程2。finalbooleanacquireQueued(finalNodenode,intarg){booleanfailed=true;尝试{布尔中断=false;对于(;;){最终节点p=node.predecessor();if(p==head&&tryAcquire(arg)){setHead(node);p.next=null;//帮助GCfailed=false;返回中断;}if(shouldParkAfterFailedAcquire(p,node)&&parkAndCheckInterrupt())interrupted=true;}}finally{if(failed)cancelAcquire(node);}}上面释放锁的整个过程可以总结如下:Thinking&LongDiagramSummaryReentrantLock
思考&长图总结ReentrantLock 我们分析了ReentrantLock的加锁和释放锁,今天最后的思考其实是:由上下文到细节,并且从细节到上下文另外,为了让大家更好的掌握ReentrantLock的AQS原理,这里给大家总结一下,首先,原理可以简单一句话解释:假设有两个线程同时获取locks操作,只能成功获取一个,另一个会进入一个队列等待,底层结合state、owner、Node队列AQS三个组件,ReentrantLock的核心加锁和释放流程可以总结如下张长图:本文由博客多发平台OpenWrite发布!