Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Java并发之AQS原理剖析

Java并发之AQS原理剖析

作者头像
程序员的时光001
发布于 2021-06-10 02:19:39
发布于 2021-06-10 02:19:39
29100
代码可运行
举报
文章被收录于专栏:程序员的时光程序员的时光
运行总次数:0
代码可运行

作者 | Yanci丶

来源 | urlify.cn/IFJ3Mb

概述:

AbstractQueuedSynchronizer,可以称为抽象队列同步器。

AQS有独占模式和共享模式两种:

  • 独占模式:

公平锁:

非公平锁:

  • 共享模式:

数据结构:

  • 基本属性:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 同步等待队列的头结点
 */
private transient volatile Node head;

/**
 * 同步等待队列的尾结点
 */
private transient volatile Node tail;

/**
 * 同步资源状态
 */
private volatile int state;
  • 内部类:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static final class Node {
    /**
     * 标记节点为共享模式
     */
    static final Node SHARED = new Node();
    /**
     * 标记节点为独占模式
     */
    static final Node EXCLUSIVE = null;

    static final int CANCELLED =  1;
    static final int SIGNAL    = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;

    /**
     *   CANCELLED:  值为1,表示当前的线程被取消
     *   SIGNAL: 值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
     *   CONDITION:  值为-2,表示当前节点在等待condition,也就是在condition队列中;
     *   PROPAGATE:  值为-3,表示当前场景下后续的acquireShared能够得以执行;
     *   0:  表示当前节点在sync队列中,等待着获取锁。
     *  表示当前节点的状态值
     */
    volatile int waitStatus;

    /**
     * 前置节点
     */
    volatile Node prev;

    /**
     * 后继节点
     */
    volatile Node next;

    /**
     * 节点同步状态的线程
     */
    volatile Thread thread;

    /**
     * 存储condition队列中的后继节点
     */
    Node nextWaiter;

    /**
     * 是否为共享模式
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * 获取前驱结点
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {    // Used to establish initial head or SHARED marker
    }

    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

主要方法解析:

  • tryAcquire/tryAcquireShared(int arg)

  独占/共享模式获取锁;由子类实现,仅仅获取锁,获取锁失败时不进行阻塞排队。

  • tryRelease/tryReleaseShared(int arg)

  独占/共享模式释放锁;由子类实现,仅仅释放锁,释放锁成功不对后继节点进行唤醒操作。

  • acquire/acquireShared(int arg)

  独占/共享模式获取锁,如果线程被中断唤醒,会返回线程中断状态,不会抛异常中止执行操作(忽略中断)。

  • acquireInterruptibly/acquireSharedInterruptibly(int arg)

  独占/共享模式获取锁,线程如果被中断唤醒,则抛出InterruptedException异常(中断即中止)。

  • tryAcquireNanos/tryAcquireSharedNanos(int arg, long nanosTimeout)

  独占/共享时间中断模式获取锁,线程如果被中断唤醒,则抛出InterruptedException异常(中断即中止);如果超出等待时间则返回加锁失败。

  • release/releaseShared(int arg)

  独占/共享模式释放锁。

  • addWaiter(Node mode)

  将给定模式节点进行入队操作。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private Node addWaiter(Node mode) {
        // 根据指定模式,新建一个当前节点的对象
        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;
            }
        }
        enq(node);
        return node;
    }
  • enq(final Node node)

  将节点设置为尾结点。注意这里会进行自旋操作,确保节点设置成功。因为等待的线程需要被唤醒操作;如果操作失败,当前节点没有与其他节点没有引用指向关系,一直就不会被唤醒(除非程序代码中断线程)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            // 判断尾结点是否为空,尾结点初始值是为空
            if (t == null) { // Must initialize
                // 尾结点为空,需要初始化
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                // 设置当前节点设置为尾结点
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
  • acquireQueued(final Node node, int arg)

  已经在队列当中的节点,准备阻塞获取锁。在阻塞前会判断前置节点是否为头结点,如果为头结点;这时会尝试获取下锁(因为这时头结点有可能会释放锁)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 当前节点的前置节点
                final Node p = node.predecessor();
                // 入队前会先判断下该节点的前置节点是否是头节点(此时头结点有可能会释放锁);然后尝试去抢锁
                // 在非公平锁场景下有可能会抢锁失败,这时候会继续往下执行 阻塞线程
                if (p == head && tryAcquire(arg)) {
                    //如果抢到锁,将头节点后移(也就是将该节点设置为头结点)
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 如果前置节点不是头结点,或者当前节点抢锁失败;通过shouldParkAfterFailedAcquire判断是否应该阻塞
                // 当前置节点的状态为SIGNAL=-1,才可以安全被parkAndCheckInterrupt阻塞线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    // 该线程已被中断
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • shouldParkAfterFailedAcquire(Node pred, Node node)

  检查和更新未能获取锁节点的状态,返回是否可以被安全阻塞。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;   // 获取前置节点的状态
        if (ws == Node.SIGNAL)
            /*
             * 前置节点的状态waitStatus为SIGNAL=-1,当前线程可以安全的阻塞
             */
            return true;
        if (ws > 0) {
            /*
             * 如果前置节点的状态waitStatus>0,即waitStatus为CANCELLED=1(无效节点),需要从同步状态队列中取消等待(移除队列)
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * 将前置状态的waitStatus修改为SIGNAL=-1,然后当前节点才可以被安全的阻塞
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
  • parkAndCheckInterrupt()

  阻塞当前节点,返回当前线程的中断状态。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1     private final boolean parkAndCheckInterrupt() {
2         LockSupport.park(this); //阻塞
3         return Thread.interrupted();
4     }
  • cancelAcquire(Node node)

  取消进行的获取锁操作,在非忽略中断模式下,线程被中断唤醒抛异常时会调用该方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//  将当前节点的状态设置为CANCELLED,无效的节点,同时移除队列
    private void cancelAcquire(Node node) {
        if (node == null)
            return;

        node.thread = null;
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        Node predNext = pred.next;
        node.waitStatus = Node.CANCELLED;
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }
  • hasQueuedPredecessors()

  判断当前线程是否应该排队。

  1.第一种结果——返回true:(1.1和1.2同时存在,1.2.1和1.2.2有一个存在)

    1.1 h != t为true,说明头结点和尾结点不相等,表示队列中至少有两个不同节点存在,至少有一点不为null。

    1.2 ((s = h.next) == null || s.thread != Thread.currentThread())为true

      1.2.1 (s = h.next) == null为true,表示头结点之后没有后续节点。

      1.2.2 (s = h.next) == null为false,s.thread != Thread.currentThread()为true         表示头结点之后有后续节点,但是头节点的下一个节点不是当前线程

  2.第二种结果——返回false,无需排队。(2.1和2.2有一个存在)

    2.1 h != t为false,即h == t;表示h和t同时为null或者h和t是同一个节点,无后续节点。

    2.2 h != t为true,((s = h.next) == null || s.thread != Thread.currentThread())为false

      表示队列中至少有两个不同节点存在,同时持有锁的线程为当前线程。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public final boolean hasQueuedPredecessors() {
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

我是【程序员二胡】,热爱技术分享,信仰终身学习,爱运动旅游,也是一个萌新up主,我们下期再见!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员的时光 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java并发:深入浅出AQS之独占锁模式源码分析
AbstractQueuedSynchronizer(以下简称AQS)作为java.util.concurrent包的基础,它提供了一套完整的同步编程框架,开发人员只需要实现其中几个简单的方法就能自由的使用诸如独占,共享,条件队列等多种同步模式。我们常用的比如ReentrantLock,CountDownLatch等等基础类库都是基于AQS实现的,足以说明这套框架的强大之处。鉴于此,我们开发人员更应该了解它的实现原理,这样才能在使用过程中得心应手。
搜云库技术团队
2019/10/18
4210
基于AQS的ReentrantLock实现原理
Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为AQS)实现的。
tunsuy
2022/10/27
4430
从ReentrantLock的实现看AQS的原理及应用
AQS作为JUC中构建锁或者其他同步组件的基础框架,应用范围十分广泛,这篇文章会带着大家从可重入锁一点点揭开AQS的神秘面纱。
美团技术团队
2019/12/10
1.7K2
从ReentrantLock的实现看AQS的原理及应用
AQS-AbstractQueuedSynchronizer源码解析(下)
获取锁显式的方法就是 Lock.lock () ,最终目的其实是想让线程获得对资源的访问权。而 Lock 又是 AQS 的子类,lock 方法根据情况一般会选择调用 AQS 的 acquire 或 tryAcquire 方法。
JavaEdge
2021/10/18
4340
Java并发编程之AQS以及源码解析
AQS(AbstractQueuedSynchronizer)是 Doug Lea 大师创作的用来构建锁或者其他同步组件(信号量、事件等)的基础框架类。
Java技术债务
2022/09/26
6840
Java并发编程之AQS以及源码解析
【原创】Java并发编程系列14 | AQS源码分析
AbstractQueuedSynchronizer是Java并发包java.util.concurrent的核心基础组件,是实现Lock的基础。
java进阶架构师
2020/03/13
4280
硬核的AQS
Java多线程在对共享资源进行访问时,如果不加以控制会存在线程安全问题,当我们使用多线程对共享资源访问时,通常会线程共享资源的进行访问线程数的控制:
shysh95
2021/04/07
2940
硬核的AQS
Java并发之AQS源码分析(一)
AQS 全称是 AbstractQueuedSynchronizer,顾名思义,是一个用来构建锁和同步器的框架,它底层用了 CAS 技术来保证操作的原子性,同时利用 FIFO 队列实现线程间的锁竞争,将基础的同步相关抽象细节放在 AQS,这也是 ReentrantLock、CountDownLatch 等同步工具实现同步的底层实现机制。它能够成为实现大部分同步需求的基础,也是 J.U.C 并发包同步的核心基础组件。
张乘辉
2019/06/14
1.2K0
Java并发之AQS源码分析(一)
Java高并发基础之AQS
曾经有一道比较比较经典的面试题“你能够说说java的并发包下面有哪些常见的类?”大多数人应该都可以说出 CountDownLatch、CyclicBarrier、Sempahore多线程并发三大利器。这三大利器都是通过AbstractQueuedSynchronizer抽象类(下面简写AQS)来实现的,所以学习三大利器之前我们有必要先来学习下AQS。
java金融
2021/02/26
3800
Java高并发基础之AQS
由浅入深逐步讲解Java并发的半壁江山AQS
synchronized 关键字是JDK官方人员用C++代码写的,在JDK6以前是重量级锁。Java大牛 Doug Lea对 synchronized 在并发编程条件下的性能表现不满意就自己写了个JUC,以此来提升并发性能,本文要讲的就是JUC并发包下的AbstractQueuedSynchronizer。
sowhat1412
2020/12/28
5520
由浅入深逐步讲解Java并发的半壁江山AQS
Java并发-AbstractQueuedSynchronizer(AQS)JDK源代码分析
 学习Java并发编程不得不去了解一下java.util.concurrent这个包,这个包下面有许多我们经常用到的并发工具类,例如:ReentrantLock,CountDownLatch,CyclicBarrier, Semaphore等。而这些类的底层实现都依赖于AbstractQueuedSynchronizer这个类,由此可见这个类的重要性。所以在Java并发系列文章中我首先对AbstractQueuedSynchronizer这个类进行分析。为了叙述简单,后续有些地方会用AQS代表这个类。
Fisherman渔夫
2020/02/19
9120
深入浅出AQS之独占锁模式
原文链接:https://www.jianshu.com/p/71449a7d01af
天涯泪小武
2019/09/27
6170
Java并发-AQS(1)
同步器本身是个抽象类,一般是通过继承的方式使用,子类通过继承同步器并实现它的抽象方法来管理同步状态。同步器提供了3个方法来修改同步状态(即state变量):getState(),setState(int newState),compareAndSetState(int expect, int update)。
lpe234
2021/02/22
2930
面试2万月薪必会知识:AQS
AQS,全称:AbstractQueuedSynchronizer,是JDK提供的一个同步框架,内部维护着FIFO双向队列,即CLH同步队列。
公众号 IT老哥
2020/09/16
4120
面试2万月薪必会知识:AQS
如何手写一个AQS?
AQS即AbstractQueuedSynchronizer,是用来实现锁和线程同步的一个工具类。大部分操作基于CAS和FIFO队列来实现。
Java识堂
2021/03/30
4490
如何手写一个AQS?
Java的AQS
AQS是AbstractQueuedSynchronizer的简称。AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架,为一系列同步器依赖于一个单独的原子变量(state)的同步器提供了一个非常有用的基础。子类们必须定义改变state变量的protected方法,这些方法定义了state是如何被获取或释放的。鉴于此,本类中的其他方法执行所有的排队和阻塞机制。子类也可以维护其他的state变量,但是为了保证同步,必须原子地操作这些变量。
用户3467126
2019/07/16
6610
Java的AQS
Java并发之AQS详解
你说我随便画的,我可不是随便画的啊,我是有bear而来,来看下AQS基本属性的代码
Java宝典
2021/03/16
3520
1.5w字,30图带你彻底掌握 AQS!
AQS( AbstractQueuedSynchronizer )是一个用来构建锁和同步器(所谓同步,是指线程之间的通信、协作)的框架,Lock 包中的各种锁(如常见的 ReentrantLock, ReadWriteLock), concurrent 包中的各种同步器(如 CountDownLatch, Semaphore, CyclicBarrier)都是基于 AQS 来构建,所以理解 AQS 的实现原理至关重要,AQS 也是面试中区分侯选人的常见考点,我们务必要掌握,本文将用循序渐近地介绍 AQS,相信大家看完一定有收获。文章目录如下
敖丙
2020/10/27
7900
1.5w字,30图带你彻底掌握 AQS!
ReentrantLock与AQS
AbstractQueuedSynchronizer(以下简写AQS)这个抽象类,因为它是 Java 并发包的基础工具类,是实现 ReentrantLock、CountDownLatch、Semaphore、FutureTask 等类的基础。
leobhao
2022/06/28
1790
ReentrantLock与AQS
【JDK1.8】JUC——AbstractQueuedSynchronizer
一、前言 在上一篇中,我们对LockSupport进行了阅读,因为它是实现我们今天要分析的AbstractQueuedSynchronizer(简称AQS)的基础,重新用一下最开始的图: 可以看到,在
joemsu
2018/05/17
6270
相关推荐
Java并发:深入浅出AQS之独占锁模式源码分析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验