AQS同步队列简单分析

AQS

AQS即AbstractQueuedSynchronizer抽象的队列同步器。使用模板模式,通过Unsafe类提供的原子性操作,park、unpark和volatile关键字使变量可见的特性,再加上一个先进先出的队列,实现了对线程的管理。只需要继承该类并重写对应方法,就可以轻松实现对线程的同步操作。

AQS内部有三个private volatile修饰的属性,分别是 state、head、tail。state用来表示各个线程要竞争的共享资源,tail和head分别指向AQS维护的先入先出队列的尾节点和头节点。tail通过CAS和自旋方式保证多线程情况下安全入队,head节点指向的节点是一个哨兵节点,该节点中并没有线程,它的主要职责是唤醒后继节点。tail和head都是延迟初始化。子类继承AQS,需要自己重写相应方法,对state进行判断自己实现获取锁的逻辑和释放锁的逻辑,AQS会将没有获取锁的线程park,在一个线程释放锁后,自动唤醒等该队列中head节点的后继节点(如果后继节点为空或者被取消,则从尾节点开始向前遍历,找到第一个不为空且不等于head节点的节点,如果该节点的状态是要被唤醒状态,则将其唤醒)

AQS维护的CLH队列是一个双向链表,每个节点包含了一个waitStatus属性,用来描述节点的状态,正常的同步节点,初始化时waitStatus=0,waitStatus>0表示节点由于中断或者超时而被取消


下面是acquire的加锁流程

// 独占模式加锁 大致流程为:如果获取锁失败,则加入等待队列
public final void acquire(int arg) {
        if (!tryAcquire(arg) && // tryAcquire  子类必须实现,自己控制是否能够获取锁。true代表该线程获取锁成功、false则获取锁失败
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 将当前线程包装为一个Node节点,并从队尾入队
            selfInterrupt();
    }


// 节点入队
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode); // 将当前线程包装为一个Node,Node的waitStats=0
        // 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)) { // CAS操作将新加入的Node置为尾节点
                pred.next = node;
                return node;
            }
        }
        enq(node); // 如果快速入队失败或者队列没有初始化,执行常规入队
        return node;
    }

//使用自旋方式,不断尝试入队,直到入队成功
 private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize 初始化操作
                if (compareAndSetHead(new Node()))
                    tail = head;  // 初始化时Node的waitStats=0 head节点是一个哨兵节点,并不存储线程
            } else { // 入队操作
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }


// 将Node包裹的线程挂起
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;  // 是否失败标记
        try {
            boolean interrupted = false; // 是否发生中断
            for (;;) {
                final Node p = node.predecessor(); // 获取当前节点的前置节点
                /**
                在第一个if中限制了在什么时候一个线程可以尝试获取锁的操作(调用tryAcquire(arg)获取锁)
                即线程必须是队列中第二个节点(因为队列中第一个节点为哨兵节点)
                该操作可以防止node中线程被虚假唤醒,即使被虚假唤醒,也会被重新的挂起
                */
                if (p == head && tryAcquire(arg)) { // 只有前置节点为head节点,该节点才有资格执行tryAcquire方法抢夺锁
                    setHead(node); // 一旦当前节点获得了锁,则出队,置为head节点
                    p.next = null; // help GC 断开原来head,在下一次垃圾回收时被收集
                    failed = false; // 操作成功
                    return interrupted; // 传递线程的中断信息
                }
                if (shouldParkAfterFailedAcquire(p, node) && //  只有线程的前一个节点的waitStatus是-1时,后面一个节点才会被挂起,新加入的节点如果没有其它操作
                    //节点状态都默认是0,通过一次shouldParkAfterFailedAcquire调用会将前一个节点状态置为-1,在下一次循环中该方法将会返回true
                    parkAndCheckInterrupt())  // 挂起线程并且检查线程是否被打断
                    interrupted = true; // 只有线程发生了中断才可能走到这一步,最后会把中断信息传递出去
            }
        } finally {
            if (failed) //如果没有按照约定的规则获取到锁,failed 就会为true
                cancelAcquire(node);
        }
    }
 private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this); // 执行挂起操作,保存挂起时线程的现场信息。线程在此处被挂起,等待其它线程的唤醒。
        return Thread.interrupted(); // 判断线程是否被中断
    }

// 释放锁
 public final boolean release(int arg) {
        if (tryRelease(arg)) { // 若要使用release方法,子类必须重写tryRelease,由子类控制锁的释放,AQS负责将等待队列中头节点的下一个线程唤醒
            Node h = head;
//如果该条件不成立,说明等待队列中没有需要唤醒的节点。通过前面分析知道如果一个节点要被park,那么它前一个节点必须是-1
            if (h != null && h.waitStatus != 0)  
                unparkSuccessor(h); // 唤醒头节点的下一个节点
            return true;
        }
        return false;
    }

private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0) 
        // 尝试将head节点waitStatus置为0,如果node.next中线程获取到锁,那么这个头节点就会被替换为node.next,如果没有获取倒锁,node.next将会被再次park,
        // 且waitStatus还会被置为-1(这个逻辑在acquireQueued中)
            compareAndSetWaitStatus(node, ws, 0); 

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) { // 如果head节点的下一个节点不可用,那么从尾节点开始向前遍历,知道找到第一个可用的node
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread); // 唤醒节点。但是该节点并不一定能获取到锁,如果是非公平锁,那么该节点中的线程还要和新加线程竞争
    }
posted @ 2021-02-26 11:37  从人间路过  阅读(225)  评论(0)    收藏  举报