AbstractQueuedSynchronizer(队列同步器,AQS)源码剖析(三)

1.1release

        接下来我们在看看如何释放锁,源码如下

public final boolean release(int arg) {//释放锁方法(独占模式) if (tryRelease(arg)) {//尝试释放锁 Node h = head; if (h != null && h.waitStatus != 0)//如果head结点不为空并且等待状态不等于0就去唤醒后继结点 unparkSuccessor(h);//唤醒等待队列的下一个节点 return true; } return false; }

protected final boolean tryRelease(int releases) { int c = getState() - releases;//获取state值,释放给定的量 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) {//如果c=0,表示锁已经释放 free = true; setExclusiveOwnerThread(null);//表示当前没有线程占用锁 } setState(c);//如果c不等于0,表示锁还没有完全释放,设置状态 return free; }

private void unparkSuccessor(Node node) {//唤醒后继节点

1int ws = node.waitStatus;//获取给定节点的等待状态 2if (ws < 0)//如果等待状态小于0,则置为0 3 compareAndSetWaitStatus(node, ws, 0); 4 5 6Node s = node.next;//获取给定节点的下一个节点 7if (s == null || s.waitStatus > 0) {//如果后继节点为null或者,等待状态>0 8 s = null;

        //从后向前遍历队列,找到一个不是取消状态的节点 for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread);//唤醒 }

        因此释放锁的过程可以总结为首先利用tryRelease尝试释放锁,如果失败,则返回false,否则调用unparkSuccessor方法去唤醒后继节点,成功则返回true。

1.2共享模式下获取锁

        在前文中,我们讲了如果在独占模式获取锁和释放锁,接下来我们在看看在共享模式下如何获取锁和释放锁,在共享模式下获取锁的方式也是三种,分别是:不响应线程中断获取acquireShared,响应线程中断获取acquireSharedInterruptibly,设置超时时间获取tryAcquireSharedNanos。

1.2.1不响应线程中断获取

        源码如下

public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0)//尝试获取锁 doAcquireShared(arg);//获取失败进入该方法 }

//尝试获取锁,由子类实现。1.负数表示获取失败;2.0表示获取成功,后继节点无法在获取;3.正数表示获取成功, //后继节点也可以获取成功 protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); }

        当获取锁失败之后,会调用doAcquireShared方法将当前线程构建成共享模式插入队列尾部,源码如下

private void doAcquireShared(int arg) { final Node node = addWaiter(Node.SHARED);//将当前线程包装成节点插入到队列尾部 boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor();//获取当前节点的前驱节点 if (p == head) { int r = tryAcquireShared(arg);//如果前驱节点为头节点,再次尝试获取锁 if (r >= 0) {//如果r>=0则表示获取锁成功 setHeadAndPropagate(node, r);//将自己设置为头节点,并   //且唤醒后面同样是共享模式的节点 p.next = null; // help GC if (interrupted) selfInterrupt();//如果在阻塞期间收到中断请求,则中断自己 failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }

private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // Record old head for check below setHead(node);//将给定节点设置为头节点

1if (propagate > 0 || h == null || h.waitStatus < 0 || 2 (h = head) == null || h.waitStatus < 0) { 3 Node s = node.next;//获取给定节点的后继节点 4 if (s == null || s.isShared()) 5 doReleaseShared();//唤醒后继节点 6}

}

        从上述代码可以看出,如果当前节点获取到锁,并且值大于0,则表示后面的节点也会获取到锁,因此当前节点就需要去唤醒后面同样是共享模式的结点,即使当前节点的后继节点为null,也会将等待状态设置为PROPAGATE来告诉后来的线程这个锁是可获取状态。

1.2.2响应线程中断获取

        源码如下

public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException();//判断线程是否中断,是抛出异常 if (tryAcquireShared(arg) < 0)//尝试获取锁 doAcquireSharedInterruptibly(arg);//获取失败进入该方法 }

private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head) { int r = tryAcquireShared(arg); if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException();//线程在阻塞过程中收到中断请求,立马抛出异常 } } finally { if (failed) cancelAcquire(node); } }

1.2.3设置超时间获取

        源码如下

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException();//响应中断 return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout);//尝试获取锁,失败则调用doAcquireSharedNanos方法 }

private boolean doAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException { if (nanosTimeout <= 0L) return false; final long deadline = System.nanoTime() + nanosTimeout; final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head) { int r = tryAcquireShared(arg); if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return true; } } nanosTimeout = deadline - System.nanoTime(); if (nanosTimeout <= 0L) return false; if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); if (Thread.interrupted()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }

        如果第一次获取锁失败会调用doAcquireSharedNanos方法并传入超时时间,进入方法后会根据情况再次去获取锁,如果再次获取失败就要考虑将线程挂起了。这时会判断超时时间是否大于自旋时间,如果是的话就会将线程挂起一段时间,否则就继续尝试获取,每次获取锁之后都会将超时时间减去获取锁的时间,一直这样循环直到超时时间用尽,如果还没有获取到锁的话就会结束获取并返回获取失败标识。在整个期间线程是响应线程中断的。

1.3共享模式下释放锁

        接下来我们再看看共享模式下是如何释放锁的,源码如下

public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) {//尝试释放锁 doReleaseShared();//成功则唤醒其他线程 return true; } return false; }

private void doReleaseShared() {

1for (;;) { 2 Node h = head;//获取队列头节点 3 if (h != null && h != tail) { 4 int ws = h.waitStatus;//获取头节点的等待状态 5 if (ws == Node.SIGNAL) {//如果head结点的状态为SIGNAL, 表明后面有人在排队 6 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) 7 continue; // loop to recheck cases 8 unparkSuccessor(h);//唤醒后继结点 9 }

            //如果head结点的状态为0, 表明此时后面没人在排队, 就只是将head状态修改为PROPAGATE else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } }

        首先调用tryReleaseShared方法尝试释放锁,该方法的判断逻辑由子类实现。如果释放成功就调用doReleaseShared方法去唤醒后继结点。另外如果没有没有后继节点,那么将状态设置为PROPAGATE。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )