探索的核心基础 ------- AQS

AQS 基础

什么是 AQS

想必大家都对其不怎么陌生,面试常考的一个知识点。

全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架

特点:

  • 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁

    1. getState - 获取 state 状态
    2. setState - 设置 state 状态
    3. compareAndSetState - cas 机制设置 state 状态
    4. 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
  • 提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList

  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet

子类主要实现这样一些方法(默认抛出 UnsupportedOperationException)

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively
    获取锁
// 如果获取锁失败
if (!tryAcquire(arg)) {
 // 入队, 可以选择阻塞当前线程 park unpark
}

释放锁

// 如果释放锁成功
if (tryRelease(arg)) {
 // 让阻塞线程恢复运行
}

AQS中有哪些数据结构

首先,我想说的是AQS 其实就是想仿照Synchnoized 做一个同步锁,并且将Synchnoized 的缺点进行优化,进行改进,所以两者联系起来去看这些东西就没有这么复杂了。

  • 首先是有一个 条件变量的内部类
public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;

内部实现了 Condition 的接口,里面的节点就是存放线程的,需要等待的线程。和Synchnoized 一样这个条件变量就相当于 其 中的 维护的一个WaitSet ,当需要的时候进行唤醒,但是不同的是,一个锁可以new 很多个条件变量,就是相当于在不同的休息室,可以精准唤醒, 而Synchnoized 不行,只有一个休息室。

  • 状态码,这个是指锁的状态码。
 private volatile int state;  

锁的状态码 初始都是1 ,当有人获取就使用CAS 修改其状态码。通过状态码来判断是否阻塞中或者是否可重入。

  • 内部还维护了一个阻塞队列,就是获取不到锁的线程进行阻塞
 private transient volatile Node head;
 private transient volatile Node tail;
  • Node 内部类,就是等待队列和阻塞队列中所维护的节点,
static final class Node 

这四个应该是比较重要的部分,接下来会逐个去分析

Node 节点

这个内部类 就是线程在队列中的状态,需要维护的节点,所以这个状态码是Node 节点中比较核心的一个关键点,代表这需不要被唤醒。

static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled. */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking. */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition. */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate.
         */
        static final int PROPAGATE = -3;

  • CANCELLED:表示线程取消了等待。如果取得锁的过程中发生了一些异常,则可能出现取消的情况,比如等待过程中出现了中断异常或者出现了timeout。
  • SIGNAL:表示后续节点需要被唤醒。
  • CONDITION :线程等待在条件变量队列中。
  • PROPAGATE :在共享模式下,无条件传播releaseShared状态。早期的JDK并没有这个状态,咋看之下,这个状态是多余的。引入这个状态是为了解决共享锁并发释放引起线程挂起的bug 6801020。(随着JDK的不断完善,它的代码也越来越难懂了 😦,就和我们自己的工程代码一样,bug修多了,细节就显得越来越晦涩)

其中CANCELLED=1,SIGNAL=-1,CONDITION=-2,PROPAGATE=-3 。在具体的实现中,就可以简单的通过waitStatus释放小于等于0,来判断是否是CANCELLED状态。

Condition 条件变量内部类的实现

  /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;

内部维护了一个等待的队列。
我们看看一些主要的方法吧

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

await 方法, 这个方法调用各种函数,不好展开说,太多了,但是表达的意思就是维护了一个等待队列,底层停止等待使用的是 LockSupport.park() ,帮助线程暂停。

 public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

唤醒队列中的一个线程

总结

  • AQS ,同步器,实现了JUC 中的核心组件
  • 解决了子类实现同步器时涉及到的大量细节问题,例如获取同步方法,FIFO同步队列
  • 采用模板方法模式,AQS 实现了大量的通用方法,子类通过继承方式实现其抽象方法来管理同步状态
  • CLH 同步队列 :FIFO 双向队列,AQS 依赖它来解决同步状态的管理问题 , 首节点唤醒,等待队列加入到CLH同步队列的尾部
  • 同步状态获取和释放:
    • 独占式 :
      • 获取锁 :
        1. 获取同步状态:acquire
        2. 相应中断: qcquireInterruptibly
        3. 超时获取: tryAcquireNanos
      • 释放锁: release
    • 共享式 :
      • 获取锁: acquireShared
      • 释放锁: releaseShared
  • 线程的阻塞和唤醒
    • 当有线程获取锁了,其他再次获取是需要阻塞,当线程释放锁后,AQS 负责唤醒线程
    • LockSupport
      • 是用来创建和其他同步类的基本线程阻塞原语
      • 是个使用LockSupport 的线程都会与一个许可关联,如果该许可可用,并且可在进程中使用,则调用 park 将会立即返回,否则可能阻塞。如果许可尚不可用,则可以调用unpark 使其可用
      • park () , unpark()

AQS 基本是提供模板方法,通过后代的子类扩展然后实现同步的一个类,但是一定要了解一些基本了原理,和一些基本的方法进行实现,一些状态码会在后代的实现,主要使用的是状态码 利用CAS 机制进行修改状态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值