java并发编程 AbstractQueuedSynchronizer(AQS)详解一

这篇具有很好参考价值的文章主要介绍了java并发编程 AbstractQueuedSynchronizer(AQS)详解一。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1 概要

AQS在类的注释上说的已经很明白,提供一个框架,用于实现依赖先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量、事件等)。此类被设计做为大多数类型的同步器的一个有用的基础类,这些同步器依赖于单个原子int值(state字段)来表示状态。
java 并发编程系列文章目录

2 技术名词解释

CAS:cas是比较并交换(compare and swap)的缩写,java对其具体实现是Usafe类,它有一系列的compareAndSwap方法

3 AQS核心方法原理

3.1 acquire(int arg)

从代码逻辑上可以看到,首先尝试获取,如果此时返回true, 执行结束。相当于获取到锁了。tryAcquire方法是抽象方法,比如公平锁和非公平锁的不同实现逻辑
如果尝试获取失败,会进入acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        //没获取到锁但是park住了,如果unpack且如果在等待的过程中发生中断走到这,进行中断位标记
        selfInterrupt();
}

此时具体看下acquireQueued(addWaiter(Node.EXCLUSIVE), arg)逻辑。

  1. addWaiter(Node.EXCLUSIVE)
private Node addWaiter(Node mode) {
	//创建Node
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        //先尝试一次原子加入链表尾部中
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //走下述逻辑,上面尝试未成功,或者此时tail == null(因为初始化的时候都是null 在后续才会设置值)
    enq(node);
    return node;
}
private Node enq(final Node node) {
	//死循环+原子性加到列表中
    for (;;) {
        Node t = tail;
        if (t == null) { // 初始化头结点和尾结点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

  1. acquireQueued(final Node node, int arg)
    因为addWaiter(Node mode)最终是死循环+原子性加到列表中,是肯定成功的。也就意味着需要阻塞当前线程了。但是有没有挽救的余地呢?
    todo1
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
        	//获取当前线程节点的前一个节点,因为在上步已经添加到tail中了
            final Node p = node.predecessor();
            //如果此时前一个节点时头节点,因为在概要中已经说明,先进先出队列,所以此时前面就一个头
            //结点,此时你就是阻塞队列第一个,那没线程阻塞啊,所以此时再尝试下获取锁
            if (p == head && tryAcquire(arg)) {
            	//此时获取到锁,那么线程安全的情况下设置头结点的node
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //尝试还是没获取到锁,那么原子设置pred.waitStatus = -1。waitStatus 状态下面会描述
            if (shouldParkAfterFailedAcquire(p, node) &&
            	//设置pred.waitStatus = -1成功了 会park阻塞线程,并返回该过程中是否被中断过
                parkAndCheckInterrupt())
                //这地方是unpark 或者中断之后的逻辑,又会回到上面去获取锁,如果pre不是头节点,又会被park住。todo1 做个标记
                interrupted = true;
        }
    } finally {
        if (failed)//正常情况下不会到这
            cancelAcquire(node);
    }
}

总结acquire(int arg):在多线程的情况下,有一个线程tryAcquire获取到锁,其余的cas进入等待队列,当然头节点的下一个或尝试下,没有获取成功的都会被park当前线程。进入阻塞状态等待被唤醒

3.2 release(int arg)

先看整体逻辑,和获取锁是一致且相反的逻辑。先尝试释放锁,释放失败直接false。还可以释放失败?? 如果锁是可重入的,那么需要把state缩减成0才算释放。
tryRelease(arg)是个接口方法,子类实现。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
    	//如果成功了,
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
private void unparkSuccessor(Node node) {
	//waitStatus下面描述
    int ws = node.waitStatus;
    if (ws < 0)
    	//把当前节点的waitStatus修改成0,如果失败了呢,好像也不影响,
        compareAndSetWaitStatus(node, ws, 0);

   	//s.waitStatus > 0就是线程取消状态,也即无效的节点,再次剔除掉找到有效的节点,从尾往头找
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //正常情况下从头取等待的线程唤醒,但是如果头节点的next节点是被取消了,那就从尾部找了,好像也不是严格的先进先出啊....
    if (s != null)
        LockSupport.unpark(s.thread);
}

3.3 acquireInterruptibly(int arg)

相对于3.1的acquire(int arg),该方法会对线程获取锁的过程中线程发生中断抛出异常

public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //先尝试获取锁,未获取到进度会抛异常的获取锁方法
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
}
//对比下
private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //区别就在这,当parkAndCheckInterrupt()返回true时,也就是获取锁结果被阻塞住了,期间发生线程中断会抛出打断异常
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

3.3 acquireShared(int arg)

获取共享锁。同比上述排它锁,相同逻辑先尝试获取共享锁tryAcquireShared(arg),然后进入doAcquireShared

public final void acquireShared(int arg) {
   if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

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) {
                	//区别在这 独占锁setHead(node);只是设置了头节点,但是共享锁在获取到锁之后,不仅要设置头节点,
                	//此时还需要判断后续节点。因为是共享的,你拿到锁之后,后续链表中挨着的共享节点也可以被unpark
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                    	//和独占锁一样,不带Interruptibly的方法只是帮你设置下中断位
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //和独占锁一样,不带Interruptibly的方法只是帮你设置下中断位
                //此时这唤醒之后 是不是又到循环上面去了,这个节点就是头结点的next节点
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
 private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head;
        //此时node就是头节点 设置进去
        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();
        }
    }
}

3.4 doReleaseShared()

for (;;) {
    Node h = head;
    if (h != null && h != tail) {
        int ws = h.waitStatus;
        if (ws == Node.SIGNAL) {
        	//可能有其他线程调用doReleaseShared(),unpark操作只需要其中一个调用就行了
        	//正常情况下就是修改成0,unpark改线程,unparkSuccessor上面已描述
            if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                continue;            // loop to recheck cases
            unparkSuccessor(h);
        }
        //如果上述失败,修改成-3 为了同时release的情况,此时设想下,如果不改成-3,只变成0 release就下不来了,就会一直hang住 
        //为啥是-3呢 我猜是符合<0的条件吧
        else if (ws == 0 &&
                 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
            continue;                // loop on failed CAS
    }
    if (h == head)                   // loop if head changed
        break;
}

3.5 releaseShared(int arg)

上面已描述

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

3.6 acquireSharedInterruptibly

按照上面的思路很明白的知道会主动抛出中断异常的获取锁的方法

3.7 hasQueuedPredecessors()

public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    //就是判断当前阻塞队列里是否有阻塞的线程node
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

总结acquireShared:可以资源共享,共享锁,相对于上述独占锁,在创建Node的时候会标记成SHARED,释放资源唤醒头结点的next节点的时候会唤醒紧挨着的SHARED Node节点吗,当然是一个唤醒另一个,因为在if (p == head)的那里循环产生的。

4 总结

如类名一样,通过Queue的Node链表来保存阻塞的线程信息,通过state字段的原子操作代表获取到的资源情况,这个字段是给子类实现用的。
关于AQS的ConditionObject详解请看详解二
java并发编程 AbstractQueuedSynchronizer(AQS)详解二文章来源地址https://www.toymoban.com/news/detail-687989.html

到了这里,关于java并发编程 AbstractQueuedSynchronizer(AQS)详解一的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 并发编程 - AQS 源码

    1. AQS 源码 2. AQS 框架具体实现 - 独占锁实现 ReentrantLock 源码 实现 ReentrantLock 的三大核心原理: LocksSuport :加锁解锁 自旋 :如果没有加锁成功就一直自旋 CAS :保证只能有一个线程可以加锁成功 queue 队列:用容器保存上面未加锁成功的阻塞线程,要解锁的时候从容器中拿出

    2023年04月21日
    浏览(10)
  • 并发编程之深入理解AQS

    并发编程之深入理解AQS

    目录 什么是AQS? AQS的特性 AQS总结 什么是AQS?        java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这些行为的抽象就是基于AbstractQueuedSynchronizer(简称AQS)实现的,AQS是一个抽象同步框架,可以

    2024年01月23日
    浏览(15)
  • 10分钟从源码级别搞懂AQS(AbstractQueuedSynchronizer)

    10分钟从源码级别搞懂AQS(AbstractQueuedSynchronizer)

    上篇文章15000字、6个代码案例、5个原理图让你彻底搞懂Synchronized有说到synchronized由object monitor实现的 object monitor中由cxq栈和entry list来实现阻塞队列,wait set实现等待队列,从而实现synchronized的等待/通知模式 而JDK中的JUC并发包也通过类似的阻塞队列和等待队列实现等待/通知模

    2024年02月10日
    浏览(35)
  • JUC并发编程之AQS原理

    JUC并发编程之AQS原理

    全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架 特点: 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个生态,控制如何获取锁和释放锁 getState - 获取 state 状态 setState - 设置 state 状态 compareAndSetState - cas 机制设置 s

    2023年04月18日
    浏览(17)
  • java并发编程:ArrayBlockingQueue详解

    java并发编程:ArrayBlockingQueue详解

    ArrayBlockingQueue 顾名思义:基于数组的阻塞队列。数组是要指定长度的,所以使用 ArrayBlockingQueue 时必须指定长度,也就是它是一个有界队列。它实现了 BlockingQueue 接口,有着队列、集合以及阻塞队列的所有方法。 ArrayBlockingQueue 是线程安全的,内部使用 ReentrantLock 来保证。A

    2024年02月08日
    浏览(10)
  • java并发编程:LinkedBlockingQueue详解

    java并发编程:LinkedBlockingQueue详解

    在集合框架里,想必大家都用过ArrayList和LinkedList,也经常在面试中问到他们之间的区别。ArrayList和ArrayBlockingQueue一样,内部基于数组来存放元素,而LinkedBlockingQueue则和LinkedList一样,内部基于链表来存放元素。 LinkedBlockingQueue实现了BlockingQueue接口,这里放一张类的继承关系图

    2024年02月08日
    浏览(13)
  • java并发编程 LinkedBlockingDeque详解

    java 并发编程系列文章目录 首先queue是一种数据结构,一个集合中,先进后出,有两种实现的方式,数组和链表。从尾部追加,从头部获取。Deque是两端都可以添加,且两端都可以获取,所以它的方法会有一系列的Last,Frist语义,添加或获取等操作会指明哪个方向的,这也是

    2024年02月10日
    浏览(9)
  • Java并发编程详解:实现高效并发应用的关键技术

    在当前的计算机领域,高效的并发编程对于Java开发人员而言变得越发重要。作为流行的编程语言,Java提供了强大的并发编程支持,使开发人员能够充分发挥多核处理器和线程的潜力,构建高性能、高吞吐量的应用程序。本文将深入探讨Java并发编程的关键技术,包括线程安全

    2024年02月13日
    浏览(17)
  • Java并发编程之线程池详解

    Java并发编程之线程池详解

    目录 🐳今日良言:不悲伤 不彷徨 有风听风 有雨看雨 🐇一、简介 🐇二、相关代码 🐼1.线程池代码 🐼2.自定义实现线程池 🐇三、ThreadPoolExecutor类 首先来介绍一下什么是线程池,线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程

    2024年02月12日
    浏览(12)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包