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

Stella981
• 阅读 659

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) {//唤醒后继节点

int ws = node.waitStatus;//获取给定节点的等待状态
if (ws < 0)//如果等待状态小于0,则置为0
    compareAndSetWaitStatus(node, ws, 0);


Node s = node.next;//获取给定节点的下一个节点
if (s == null || s.waitStatus > 0) {//如果后继节点为null或者,等待状态>0
    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);//将给定节点设置为头节点

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

}

        从上述代码可以看出,如果当前节点获取到锁,并且值大于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() {

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

            //如果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。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
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
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这