JUC并发包详解AQS同步队列

在这里插入图片描述

一、AQS介绍

在JUC并发包中,AQS为其最关键的作用,全称为abstractQueuedSynchroinzed同步器,为信号量semaphore、同步锁的基础抽象类。

在这里插入图片描述
其中内部主要有二大块

  1. state

共享资源,通过并发操作state修改改值为1,如果修改成功则表示获得锁。

  1. FIFO队列

该队列为CLH队列的变形队列,通过引入双向队列,采取自旋加堵塞的方式提高性能,其中核心为head节点、tail节点。

二、AQS队列

    /**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;

一个队列肯定也有队头标记、队尾标记,这里的队头标记就是Head,head是一个引用变量,会指向AQS队列的第一个节点元素,tail也是一个引用变量,也会指向AQS最后一个节点。

因为会同时多个线程节点会竞争锁,因此每一个节点都需要cas成为队尾。

2.1 head和tail的初始化

在上述注释中,明白head和tail一开始是为空的,只有多个线程竞争锁,才会进行初始化。

/**
* 当线程进行acquire失败的时候,则说明当前锁被其他线程获得,因此该线程需要进行封装成Node节点,并且插入到AQS队列中。
**/
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            // 如果t也就是tail为空,则说明AQS队列没有值,则需要初始化head和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;
                }
            }
        }
    }

由上面代码可知,如果aqs同步锁,一开始有一个线程获得lock,则会直接执行,当第二个过来,因为state的值为1,则需要将该线程设置为node进行入队伍,这里就会初始化对应的tail和head,可知这里的head和tail都是指向该节点。

在这里插入图片描述
当这个时候第三个线程进来,则aqs队列则会插入第三个线程c,对应tail也会发生改变。

在这里插入图片描述

 node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }

2.2 waitstatus状态值

waitStatus有如下几种状态:

  1. CANCELLED(1):当前节点取消获取锁。当等待超时或被中断(响应中断),会触发变更为此状态,进入该状态后节点状态不再变化;
  2. SIGNAL(-1):后面节点等待当前节点唤醒;
  3. CONDITION(-2):Condition 中使用,当前线程阻塞在 Condition,如果其他线程调用了 Condition 的 signal 方法,这个结点将从等待队列转移到同步队列队尾,等待获取同步锁;
  4. PROPAGATE(-3):共享模式,前置节点唤醒后面节点后,唤醒操作无条件传播下去;
  5. 0:中间状态,当前节点后面的节点已经唤醒,但是当前节点线程还没有执行完成。

但是对于AQS的同步锁的队列需要去除CONDITION状态,在这里我们先不考虑PROPAGATE状态;

2.2.1 CANCELLED

waitstatus > 0 的时候只有一种状态为CANCElED也就是当前节点为取消状态,如果当前节点被等待超时了或者被中断了,则会变成该状态。

  1. 赋值时机

ReentrantLock提供tryLock(int time,TimeUnit), lockInterrupt()方法,也就是说获得锁的时候会去设置超时时间,或者设置可中断,在节点等待过程中如果发送了超时、中断则会将该节点的waitstatus设置为1,并且返回lock方法为false,表示未获得锁。
在这里插入图片描述当线程被取消时,它对应的节点会被标记为取消状态,并从同步队列中清除

2.2.2 waitstatus初始状态0

我们发现这里有一个状态为0, 0表示为节点未加入队列的状态,也就是初始状态。

  1. 如果该节点为tail节点,则该状态一直为0,如果该节点有后续节点,后续节点在park之前会将pre节点设置为-1也就是SIGNAL, 但是如果该节点为第一个节点,则还是会被设置为-1,因为它自己的pre节点就是本身。具体参考shouldParkAfterFailedAcquire。

在这里插入图片描述

2.2.3 SIGNAL状态

我们在2.2.2可知道,如果节点进行park会将前节点的值设置为signal,那么这里的signal有什么用呢?

signal的状态表示:后面节点等待当前节点唤醒;
在这里插入图片描述
在release的时候会去唤醒后续节点。
在这里插入图片描述

三、如何基于AQS实现一个可重入锁

AQS提供了如下自定义方法

  1. tryAcquire
  2. tryRelease
  3. tryAcquireShared
  4. tryReleaseShared
  5. isHeldExclusively : 判断是否为独占锁

在编写一个锁的话,如果是独占锁,则需要实现tryAcquire和tryRelease和isHeldExclusively。

其中编写需要实现的就是对state进行操作,也就是state是为共享变量,如果state==0则表示该aqs锁未被占用,如果state>0,则表示该aqs被锁占用。

如果要使用一个锁对象,类似ReentrantLock(可重入锁),则需要实现Lock接口,该接口提供如下方法:
在这里插入图片描述
则需要在锁对象中创建继承aqs的内部类,并且实现对应的tryAcqurie方法、tryRelase方法以及isHeldExclusively方法。

tryAcqurie:获得锁
tryRelease: 释放锁
isHeldExclusively: 判断当前线程是否拥有锁

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值