通过源码了解AQS

AQS介绍

AbstractQueuedSynchronizer,简称AQS, 提供了一种实现同步器的框架. 该框架提供可以阻塞的加锁和释放锁. 其底层依赖FIFO双向链表.

在介绍AQS之前,首先对锁进行简单的介绍. 按照不同维度, 对锁进行简单划分:

  1. 独占锁/共享锁
  2. 可重入锁/不可重入锁
  3. 公平锁/非公平锁

基于AQS框架, 可以实现上面提及的锁, 而AQS的作用是降低实现的复杂度.

锁的状态

在AQS中, 保存着一个int类型的变量, 该变量是用来标识锁的状态. 注意该变量是一个int类型的值, 不是boolean类型. 所以这个锁的状态并不是只有两种状态(0没获取,1获取), 而是可以有多种状态, 比如表示可以允许几个线程获取锁或按位标识锁的状态, 比如前多少位表示读的状态, 后多少位表示写的状态(ReentrantReadWriteLock).

先看下源码中出现该state的位置:
state有关方法
分析源码得知, state变量是私有的, 实现AQS者不能直接访问, 而访问的方式只有通过getState()setState(int).

双向链表介绍

在AQS底层封装了双向链表, 该链表用来保存等待获取锁的线程. 每一个节点代表一个线程, 并且每个节点都有等待的状态. 当有线程想要获取锁, AQS会将线程封装成节点(Node),然后加入到队列中.

正常情况下双向链表如下:
正常双向链表

入队时会将新加入的Node添加到链表尾部,并且设置新的队尾. 设置队尾通过CAS操作,只有通过CAS设置队尾成功,才会更新前一个节点的后继. 在入队操作中有个特殊的情况是队列未初始化(tail == null),这种情况需要先将队列初始化.

下面来介绍两个涉及入队的方法, enqaddWaiter.

// 节点入队的代码.
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. 入队时, 如果发现双向链表未初始化, 则会先进行初始化.
  2. 双向链表中表头初始有一个哨兵节点, 这个哨兵节点后续会被移除.
  3. compareAndSetHead/Tail 操作是原子操作, 能够保证多线程情况下, 若与期望不相等, 则设置失败.并且返回false.

下图代表双向链表初始化.
双向链表初始化

addWaiter是对enq的一个封装, 它会尝试添加到队尾,如果失败了之后, 才会降为调用enq方法. 另外 addWaiter 方法还有一个重要的功能, 是将当前线程封装为节点Node.

// 以给定的mode添加一个node. 该node会把当前线程封装进去.
private Node addWaiter(Node mode) {
   
    // 根据节点的mode创建node
    Node node = new Node(Thread.currentThread(), mode);
    // 尝试将新加入的node添加到队尾,
    Node pred = tail;
    if (pred != null) 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值