AQS抽象同步队列

32 阅读6分钟

一、是什么

字面意思: 抽象的队列同步器

技术解释:是用来实现锁或者其它同步器组件的公共基础部分的抽象实现,是重量级基础框架及整个JUC体系的基石,主要用子解决锁分配给"谁"的问题

整体就是一个抽象的FIFO 队列 来完成资源获取线程的排队工作,并且通过一个 int类变量 表示持有锁的状态

  • 为实现阻塞锁和相关的同步器提供一个框架,它是依赖于先进先出的一个等待
  • 依靠单个原子int值来表示状态,通过占用和释放方法,改变状态值

整体就是一个抽象的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态

二、AQS为什么是JUC内容最重要的基石

和AQS有关的

  1. ReentrantLock
  2. CountDownLatch
  3. ReentrantReadWriteLock
  4. Semaphore

ReentrantLock

CountDownLatch

ReentrantReadWriteLock

Semaphore

理解锁和同步器的关系

锁,面向锁的使用者: 定义了程序员和锁交互的使用层面API,隐藏了实现细节,你调用了即使用(初级)

同步器,面向锁的是现在

三、能干嘛

1.加锁会导致阻塞

有阻塞就会排队,要排队就必须要队列

抢到资源的线程直接使用处理业务。抢不到资源的必然涉及一种排队等候机制。抢占资源失败的线程继续去等待(类似银行业务办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),但等候线程仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。

既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?

如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS同步队列的抽象表现。它将要请求共享资源的线程及自身的等待状态封装成队列的结点对象(Node),通过CAS、自旋以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步的效果。

AQS 使用一个Volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值的修改

AQS中的int变量:同步状态state成员变量。

Node的int变量 : Node的等待状态withState成员变量 volatile int waitStatus

等候区其他顾客(其他线程)的等待状态

队列中每个排队的个体就是一个Node

2.AQS内部体系架构

Node结点源码

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

/** 
线程被取消了
*/
static final int CANCELLED =  1;
/**
后继线程需要唤醒
*/
static final int SIGNAL    = -1;
/** 
等待condition唤醒
*/
static final int CONDITION = -2;
/**
*共享式同步状态获取将会无条件地转播下去
*/
static final int PROPAGATE = -3;

/**
初始值为0,状态是上面的几种
*/
volatile int waitStatus;

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

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

/**
* The thread that enqueued this node.  Initialized on
* construction and nulled out after use.
*/
volatile Thread thread;

Node属性说明

四、ReentrantLock为例子讲解lock()方法

Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的。

我们点开ReentrantLock的源码可以看到,它里面有三个静态内部类 Sync、NonfairSync、FairSync

其中 Sync 继承 AQS

NonFairSync、FairSync 继承 Sync

通过Lock()、unLock() 方法可以看出其本质还是在操作 sync继承的父类AQS

公平锁和非公平锁的区别

image.png


以非公平锁ReentrantLock()为例作为突破走起,方法lock()

对比公平锁和非公平锁的tryAcquire()方法的实现代码,其实差别就在于非公平锁获取锁时比公平锁中少了一个判断IhasQueuedPredecessors()

hasQueuedPredecessors()中判断了是否需要排队,导致公平锁和非公平锁的差异如下:

公平锁:公平锁讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中;

非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。也就是说队列的第一个排队线程苏醒后,不一定就是排头的这个线程获得锁,它还是需要参加竞争锁(存在线程竞争的情况下),后来的线程可能不讲武德插队夺锁了。

分析acquire源码:

非公平锁的lock()方法

公平锁的lock()方法

接下来分析lock() 方法里面的一个acquire方法

场景: 三个人A、B、C 去银行办理业务,首先A先办理业务,抢占线程,其次BC来分析AQS的执行流程

1.tryAcquire

       final boolean nonfairTryAcquire(int acquires) {
           // 获取当前线程
            final Thread current = Thread.currentThread();
    	 // 获取State 值
            int c = getState();
        // 若为0 则可以抢占
            if (c == 0) {
                // CAS 设置state 值
                if (compareAndSetState(0, acquires)) {
                     // 设置AQS 拥有线程为当前线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 若AQS 拥有线程为当前线程
            else if (current == getExclusiveOwnerThread()) {
                // 可重入锁
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                // 设置当前State值
                setState(nextc);
                return true;
            }
            return false;
        }

2.addWaiter(Node.EXCLUSIVE)

若 tryAcquire抢占锁失败 则进行addWaiter

    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
        enq(node);
        return node;
    }

因为第一次进入为空,所以enq进入队列,循环

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                // 尾头节点为空, 系统自动new 一个节点为头节点
                if (compareAndSetHead(new Node()))
                    //设置头节点为尾部节点
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

头尾节点设置成功

双向链表中,第一个节点为虚节点(也叫哨兵节点) ,其实并不存储任何信息,只是占位。真正的第一个有数据的节点,是从第二个节点开始的。

接下来 tail!=null了

至此,enq方法结束,B成功进入队列

// addwaiter 中的部分代码
      Node pred = tail;
    	// 刚刚B已经入队 tail 已经不是null
        if (pred != null) {
            node.prev = pred;
            // 设置当前节点为 tail
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }

至此 C进入队列

走的是下面这段代码逻辑

3.acquireQueued(addWaiter(Node.EXCLUSIVE))

稳稳地挂起线程

首先是B 先进入acquireQueued , 然后再试C 进入acquireQueued

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //自旋大循环
            for (;;) {
                // 获取前驱结点
                final Node p = node.predecessor();
                // 若前驱结点为 头结点 则再次去tryacquire
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 若上面失败,则进入 shouldParkAfterFailedAcquire
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                // 可以出队
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire()方法

	/**
    pred 前置节点	
    **/
	private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus; 
        //判断前驱结点的withstatus状态 若为-1 返回ture
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            //第一次尽量前驱一般为0
            //若前驱结点为0,的话 ,则设置他的withstatus为-1
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        // 第一次进来为0 则返回fasle
        return false;
    }

若shouldParkAfterFailedAcquire 第二次进来,因为上一次将前驱结点的withstatus 设置为-1

则这次会进行

    private final boolean parkAndCheckInterrupt() {
        //进入此方法 挂起线程B 程序不会向下执行
        LockSupport.park(this);
        // 根据park 方法api描述,程序在下述三种情况会继续向下执行
        // 1. 被unpark
        // 2. 被中断(interrupt)
        // 3. 其他不合逻辑的返回才会继续向下执行
        return Thread.interrupted();
    }

至此 B线程操作结束,进行 C线程操作

因为C的前驱结点不为head了,所以直接进行shouldParkAfterFailedAcquire()方法

基本步骤和B一样了,也是挂起线程

五、unlock()

  1. sync.release(1)
  2. tryRelease(arg)
  3. unparkSuccessor
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

1.tryRelease()

    protected final boolean tryRelease(int releases) {
            //state 扣减一
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //若为零,释放锁业务办理完成
            if (c == 0) {
                free = true;
                //设置窗口没人 为null
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

2.unparkSuccessor()

执行 unparkSuccessor()方法

    private void unparkSuccessor(Node node) {

        int ws = node.waitStatus;
    	// 若waitStatus 小于0 则设置为0 刚刚前面shouldParkAfterFailedAcquire()方法
		//设置为了-1
        if (ws < 0)
            //头结点的WS重新改为0
            compareAndSetWaitStatus(node, ws, 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;
        }	
    	// 若结点的下一个结点不为null,则唤醒挂起的线程
        if (s != null)
            //unpark线程 就可以获取锁了。
            LockSupport.unpark(s.thread);
    }

注意:非公平锁的情况,不是排队取获取锁,会有 不讲武德 的情况去抢占锁

3.acquireQueued

当返回true时,重新设置头结点

六、cancelAcquire(node)

终止进入队列,不等了,退出队列

  1. 队尾结点
  2. 中间结点
  3. 许多结点
    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;

        //设置节点的waitstatus 、
        node.waitStatus = Node.CANCELLED;

        // 如果取消的节点是 tail
        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
        }
    }