AQS内部采用CLH队列。CLH队列是由节点组成。内部的Node节点包含的状态有

static final int CANCELLED =  1;

static final int SIGNAL    = -1;

static final int CONDITION = -2;

static final int PROPAGATE = -3;

其中取消状态表示任务的取消,SIGNAL状态表示后续节点需要唤醒,CONDITION表示等待状态,PROPAGRATE表示的是传播状态通常用于共享锁的释放。初始节点的状态为0。

AQS中比较重要的操作包括:

public final void acquire(int arg);

public final void acquireInterruptibly(int arg);

public final void acquireShared(int arg);

public final void acquireSharedInterruptibly(int arg);

protected boolean tryAcquire(int arg);

protected int tryAcquireShared(int arg);

public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException;

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException;

其中:public final void acquire(int arg) {
           if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

selfInterrupt();
   }

其中tryAcquire方法为抽象方法。不同的子类有不同的实现方式。AQS中该方法的实现知识抛出了一个异常。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;//标记是否成功拿到资源
    try {
        boolean interrupted = false;//标记等待过程中是否被中断过
       
        //自旋的过程
        for (;;) {
            final Node p = node.predecessor();//拿到前驱
            //如果前驱是head,即该结点已成老二,那么便有资格去尝试获取资源(可能是老大释放完资源唤醒自己的,当然也可能被interrupt了)。
            if (p == head && tryAcquire(arg)) {
                setHead(node);//拿到资源后,将head指向该结点。所以head所指的标杆结点,就是当前获取到资源的那个结点或null。
                p.next = null; // setHead中node.prev已置为null,此处再将head.next置为null,就是为了方便GC回收以前的head结点。也就意味着之前拿完资源的结点出队了!
                failed = false;
                return interrupted;//返回等待过程中是否被中断过
            }
           
            //如果自己可以休息了,就进入waiting状态,直到被unpark()
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;//如果等待过程中被中断过,哪怕只有那么一次,就将interrupted标记为true
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

可以从该方法中看出。这里会继续尝试去获取一下资源

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;//拿到前驱的状态
    if (ws == Node.SIGNAL)
        //如果已经告诉前驱拿完号后通知自己一下,那就可以安心休息了
        return true;
    if (ws > 0) {
        /*
         * 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。
         * 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链,稍后就会被保安大叔赶走了(GC回收)!
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
         //如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它拿完号后通知自己一下。有可能失败,人家说不定刚刚释放完呢!
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

在该方法中会检测当前节点中前面的节点是否有CANCELLED状态的如果有。待会儿后续的操作这些节点会被GC回收。

如果一切正常当前节点的前一个节点会被设置为SIGNAL状态。

1 private final boolean parkAndCheckInterrupt() {
2     LockSupport.park(this);//调用park()使线程进入waiting状态
3     return Thread.interrupted();//如果被唤醒,查看自己是不是被中断的。
4 }

线程进入waiting状态。线程被唤醒的方式有被unpark和被中断。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;//找到头结点
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//唤醒等待队列里的下一个线程
        return true;
    }
    return false;
}

这里tryRelease也是一个抽象方法不同的子类有不同的实现。

private void unparkSuccessor(Node node) 内部首先会设置当前节点的状态为初始状态。同时找到一个有效的后继节点的状态小于0的进行节点的释放。 LockSupport.unpark(s.thread);//唤醒对应的线程。

java中AQS源码分析的更多相关文章

  1. Java中ArrayList源码分析

    一.简介 ArrayList是一个数组队列,相当于动态数组.每个ArrayList实例都有自己的容量,该容量至少和所存储数据的个数一样大小,在每次添加数据时,它会使用ensureCapacity()保 ...

  2. Java中HashMap源码分析

    一.HashMap概述 HashMap基于哈希表的Map接口的实现.此实现提供所有可选的映射操作,并允许使用null值和null键.(除了不同步和允许使用null之外,HashMap类与Hashtab ...

  3. java中LinkedList源码分析

    ArrayList是动态数组,其实本质就是对数组的操作.那么LinkedList实现原理和ArrayList是完全不一样的.现在就来分析一下ArrayList和LinkeList的优劣吧LinkedL ...

  4. ReentrantLock 与 AQS 源码分析

    ReentrantLock 与 AQS 源码分析 1. 基本结构    重入锁 ReetrantLock,JDK 1.5新增的类,作用与synchronized关键字相当,但比synchronized ...

  5. 并发-AQS源码分析

    AQS源码分析 参考: http://www.cnblogs.com/waterystone/p/4920797.html https://blog.csdn.net/fjse51/article/d ...

  6. 【原】Spark中Client源码分析(二)

    继续前一篇的内容.前一篇内容为: Spark中Client源码分析(一)http://www.cnblogs.com/yourarebest/p/5313006.html DriverClient中的 ...

  7. 【JAVA】ThreadLocal源码分析

    ThreadLocal内部是用一张哈希表来存储: static class ThreadLocalMap { static class Entry extends WeakReference<T ...

  8. 【Java】HashMap源码分析——常用方法详解

    上一篇介绍了HashMap的基本概念,这一篇着重介绍HasHMap中的一些常用方法:put()get()**resize()** 首先介绍resize()这个方法,在我看来这是HashMap中一个非常 ...

  9. 【Java】HashMap源码分析——基本概念

    在JDK1.8后,对HashMap源码进行了更改,引入了红黑树.在这之前,HashMap实际上就是就是数组+链表的结构,由于HashMap是一张哈希表,其会产生哈希冲突,为了解决哈希冲突,HashMa ...

随机推荐

  1. 十六进制颜色码及其表示-(6 digit color code)

    我们知道对于RGB颜色系统,颜色是由三个256位的十进制数值表示的: (R:0-255,G:0-255,B:0-255) 那么一个三元组可以确定一种颜色. 然而,在很多配置文件中颜色并不是直接用十进制 ...

  2. 【Java】错误: 需要class, interface或enum

    今天在用cmd实现mvn package操作时跳出来的报错! 网上搜索到的结论是因为编码问题而产生的,具体原因就不深究了 要详细了解可以查看以下链接https://blog.csdn.net/qq_3 ...

  3. hdoj3791

    题目: Problem Description 判断两序列是否为同一二叉搜索树序列   Input 开始一个数n,(1<=n<=20) 表示有n个需要判断,n= 0 的时候输入结束.接下去 ...

  4. 【转】Windows下PATH等环境变量详解

    [转]“肖凡的专栏” 博客,请务必保留此出处http://legend2011.blog.51cto.com/3018495/553255 在学习JAVA的过程中,涉及到多个环境变量(environm ...

  5. Redis快照原理详解

    本文对Redis快照的实现过程进行介绍,了解Redis快照实现过程对Redis管理很有帮助. Redis默认会将快照文件存储在Redis当前进程的工作目录中的dump.rdb文件中,可以通过配置dir ...

  6. 动态自动配置Bean

    概览 接口Condition 用于基于条件的自动配置,和注解@Conditional配合使用,可实现JavaBean的动态自动配置 自定义实现动态配置Bean 定义一个接口和两个实现类 定义两个Con ...

  7. Spring WebFlux 01 (原理及使用场景)

    一.什么是 Spring WebFlux 好多人以为Spring WebFlux就是Spring MVC的升级版,其实不然,那到底什么是Spring WebFlux呢,首先就要搞清楚Spring We ...

  8. jQurey zTree Demo 3.5

    https://jeesite.gitee.io/front/jquery-ztree/3.5/demo/cn/index.html

  9. 使用vs2019加.net core 对WeiApi的创建

    vs2019创建webapi 1.创建新的项目 2.选择.NET CORE的ASP .NET CORE WEB应用程序 3.定义项目名称和存放地点 4.选择API创建项目 5.删除原本的无用的类 6. ...

  10. gRPC by .net core 3.x——概念、语法、编译

    什么是grpc? grpc来自大名鼎鼎的谷歌,孵化于CNCF基金会(docker.k8s同样出自这个基金会).它是一款高性能.开源.通用的rpc框架,你可以通过它来定义rpc的请求和响应.它基于htt ...