AQS:AbstractQuenedSynchronizer抽象的队列式同步器

一 简介

AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包
实现了AQS的锁有:自旋锁、互斥锁、读锁写锁、条件产量、信号量、栅栏都是AQS的衍生物。AQS维护了一个volatile int state和一个FIFO线程等待队列,多线程争用资源被阻塞的时候就会进入这个队列。

二 AQS的设计

AbstractOwnableSynchronizer为抽象类,目的是为java.util.concurrent.locks包下的类提供一个公共的多线程通信的框架,也不希望使用者直接去使用AQS(new AQS())的方式。

几个关键的方法

protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}

protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}

protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}

protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}

这处设计为非abstract,并且抛出了异常。原因就是:

  1. AQS的目的是需要子类去自己实现相关的方法,如果不需要也可以不实现。这里只是提供一种想法、思路给子类设计者。提高框架的可扩展性。
  2. 如果子类没有实现并且不小心掉用了该方法,会通过抛异常的方式来进行通知。

AbstractQuenedSynchronizer的父类

java.util.concurrent.locks.AbstractOwnableSynchronizer#exclusiveOwnerThread 获取到锁的线程对象

三 源码关键元素

1、state

java.util.concurrent.locks.AbstractQueuedSynchronizer#state

private volatile int state; 使用volatile修饰来保证state的多线程可见性

就是共享资源,其访问方式有如下三种:

final int getState();

final void setState();

final boolean compareAndSetState();

2、Node

java.util.concurrent.locks.AbstractQueuedSynchronizer.Node

  • 由多个Node节点构成了AQS的阻塞的双向队列.
  • 分为独占队列和共享队列

主要成员变量:

volatile Node prev;

volatile Node next;

volatile Thread thread;//将此节点排入队列的线程。在构造时初始化,使用后为null。

Node nextWaiter;链接到等待条件或特殊值SHARED的下一个节点。因为条件队列只有在独占模式下保持时才被访问,所以我们只需要一个简单的链接队列来在节点等待条件时保持它们。然后将它们转移到队列中以重新获取。由于条件只能是独占的,我们通过使用特殊值来指示共享模式来保存字段。

static final int CANCELLED = 1; waitStatus值表示线程已取消

static final int SIGNAL = -1;waitStatus值,指示后续线程需要取消标记,也就是需要被唤醒unpark

static final int CONDITION = -2; 条件队列使用

static final int PROPAGATE = -3; 共享模式使用

volatile int waitStatus; 初始化值为0,重点就是 0 -1两个状态

          +------+  prev  +-----+          +-----+
  head|         |  <----   |        | <---- |        |  tail
          +------+           +-----+         +-----+
  

3、从ReentrantLock的lock unlock方法入手看AQS源码

        ReentrantLock reentrantLock = new ReentrantLock();

        try{
            reentrantLock.lock();
            System.out.println("获取锁成功");
//            reentrantLock.lockInterruptibly();
        }finally {
            reentrantLock.unlock();
        }

(1)不公平锁与公平锁的实现

Unsafe是jdk提供的一个直接访问操作系统资源的工具类(底层c++实现),它可以直接分配内存,内存复制,copy,提供cpu级别的CAS乐观锁等操作。

Unsafe位于sun.misc包下,jdk中的并发编程包juc(java.util.concurrent)基本全部靠Unsafe实现,由此可见其重要性。

 static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            //CAS的方式尝试去争夺锁(barge硬闯),并且设置当前的执行线程,
            //这里的CAS通过unSafe实现
            //如果没有成功就正常走入队逻辑
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
/**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

(2)acquire(1)

    /**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
tryAcquire(1) 尝试入队

hasQueuedPredecessors()

只有当hasQueuedPredecessors == false的时候,当前线程才可以调用compareAndSetState去尝试的获取锁,

最终目的:就是实现公平,如果队列中存在元素的情况下,当前线程就要去排队;如果当前线程位于队列头部或者队列是空的,就可以去尝试获取锁。

如果h != t: 成立,也就是说明头节点==尾节点,也就是队列是空的.

h.next == null 成立,也就是头节点的下一个节点是null

s.thread != Thread.currentThread()成立,头节点的下一个节点的线程不是当前线程

    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        // 这一点的正确性取决于head在tail之前被初始化,以及如果当前线程在队列中的第一个,那么head.next是否准确。
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

 (2)addWaiter

通过当前的模式(公平或者非公平模式),创建并且将当前线程的node入队

   /**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    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;
        //1 说明队列已经初始化,直接将node加入到队列中,并且设置为tail
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //**初始化队列**
        // 第一步:head与tail同事指向一个新newNode(Thread=null)
        // 第二步:head指向newNode,newNode.next = node,node.prev = newNode,tail指向node。
        enq(node);
        return node;
    }

    /**
     * 初始化头尾节点(如果需要的话),并且将当前线程的node加入到双向队列中
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) {
        for (;;) {
            //t用来保存原来的尾节点
            Node t = tail;
            //1、如果尾节点为null,新建一个新的头节点并且设置尾指向头
            //2、进入下次循环
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //当前节点的pre指向尾
                node.prev = t;
                //设置当前节点为尾节点,此时尾节点变了,所以只能使用t.next指向node
                if (compareAndSetTail(t, node)) {
                    //尾节点的next指向当前节点
                    t.next = node;
                    return t;
                }
            }
        }
    }

如图所示:

head头结点的Node结构:Thread = null, prev = null, next = 当前线程的nowNode
nowNode: Thread = currentThread,prev = head头节点,next = null
tail:指向nowNode,也就是说当前节点就是尾节点

t:就是中间的node节点,tail、head都指向该节点

图一:理想的情况下

                                               

图二:并发的情况下

当t1去CAS设置头节点的时候,发现compareAndSetHead(new Node())执行失败,此时t2抢先执行成功了。

那么t1就要去再一次执行for(;;),这一次执行到else,设置node.prev = t。然后向下继续执行compareAndSetTail(t, node),这里假设t2已经执行compareAndSetTail(t, node)完成,这时t1指定是执行失败的,那么又一次去执行下一次for(;;)

此时的状态如下图,这一次再次执行到else,然后设置t1的prev为t2,t1为尾节点,t2.next = t1,结束。

最终状态如下图所示:

(3)addWaiter的思考

Java9的优化,将Node tail 尾节点不为空的的情况抽象出来放到最开始的if逻辑中。

(4)acquireQueued

因为ReentrantLock是独占锁,是不可以响应中断的锁(加入到同步队列中的线程,发生中断时并不会响应异常,也不会取消该线程的任务),也就是说即使产生了中断Node中的线程也不会被移除。

waitStatus的默认值是0
static final int CANCELLED = 1;  不会出现
static final int SIGNAL = -1;  当前Node的next节点可以被唤醒
static final int CONDITION = -2; 条件队列,不会出现
static final int PROPAGATE = -3; 共享模式下的状态,不会出现

总结:ReentrantLock中会出现的waitStatus状态就两种0、-1

    /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;//获取锁是否成功
        try {
            boolean interrupted = false;//是否产生中断
            for (;;) {
                // 当前节点的前驱节点是否是头节点
                final Node p = node.predecessor();

                //1、测试初始化完第一个线程之后,head就是指向的一个虚拟节点,
                //这个虚拟节点的next就是第一个线程的节点。
                //2、如果当前节点的前驱节点是头节点,也就是代表当前节点是队列中的第一个节点,                       
                //此时tryAcquire(调用公平或者非公平的逻辑)尝试去获取锁
                if (p == head && tryAcquire(arg)) {

                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //如果当前节点获取锁失败之后,执行到这里
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }


    /**
     * Sets head of queue to be node, thus dequeuing. Called only by
     * acquire methods.  Also nulls out unused fields for sake of GC
     * and to suppress unnecessary signals and traversals.
     *
     * @param node the node
     */
    private void setHead(Node node) {
        head = node;
        // 目的是方便GC
        node.thread = null;
        node.prev = null;
    }

shouldParkAfterFailedAcquire

1、检查或者更新当前线程的Node中的waitStatus状态,在获取锁失败之后,当前线程是否应该阻塞。如果返回true代表可以阻塞。这个方法是在获取锁循环中最主要的信号控制方法。最终的目的就是将当前的线程的前驱节点的waitStatus状态设置为SIGNAL。这样当前线程才可以去阻塞。

如果else执行完成之后会继续执行acquireQueued中的for(;;)自旋,当前的Node又得到了一次获取锁的机会。(重点)

2、shouldParkAfterFailedAcquire==true之后执行parkAndCheckInterrupt(),也就是让当前的线程进行阻塞LockSupport.park(this)


    /**
     * Checks and updates status for a node that failed to acquire.
     * Returns true if thread should block. This is the main signal
     * control in all acquire loops.  Requires that pred == node.prev.
     *  
     * @param pred node's predecessor holding status 当前节点的前驱节点
     * @param node the node。当前节点
     * @return {@code true} if thread should block 返回当前线程是否阻塞
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        //如果前驱节点是SIGNAL,就可以阻塞
        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) { //在ReentrantLock中这里不会走到,所以如果上面不成立就走到else中
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //这里将ws设置为SIGNAL,去让当前线程可以阻塞;
     
            /*
             * 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.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

4、lock()方法的流程图

5、unlock()的实现

    public void unlock() {
        sync.release(1);
    }


    

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            //满足这种情况就可以进行后继节点的唤醒
            //1、即头节点不是null,说明队列中有Node
            //2、头节点waitStatus不是0,说明waitStatus == -1 这是必然的条件
            //这样才可以进行后继线程的唤醒
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease

尝试正去释放当前线程占用的锁资源,ReentrantLock是可重入锁,必须占用锁的线程释放了所有的锁计数才返回true,也就是state == 0

        protected final boolean tryRelease(int releases) {
            //可重入锁的加锁次数-1
            int c = getState() - releases;
            //可以解锁的线程必然是加锁的线程
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //可重入锁的加锁次数==0才可以释放锁
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

unparkSuccessor

唤醒头节点的后继线程

    /** 
     * Wakes up node's successor, if one exists.
     *
     * @param node the node 头节点
     */
    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.
         */
        //将头节点的waitStatus更改为初始状态
        int ws = node.waitStatus;
        if (ws < 0)
            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.
         */
        //这里的逻辑是s.waitStatus > 0的逻辑并不包含在ReentrantLock,先过滤掉
        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)
            LockSupport.unpark(s.thread);
    }

思考题

思考1

唤醒之后的线程会执行怎么样的逻辑?

答:唤醒之后从acquireQueued方法中的parkAndCheckInterrupt中继续执行。

  • Thread.interrupted(); 返回线程的中断状态(清除中断的状态),如果在阻塞的过程中发生中断了,true 否则 false
  • acquireQueued中的interrupted就是等于 true or false,这里只是一个线程的标志位。
  • 此时继续执行for(;;)自旋,当前节点的前驱节点如果是头节点的话 或者 获取锁失败的话,又会执行到shouldParkAfterFailedAcquire这里,继续阻塞该线程。。。
  • 通过这里的执行可以得出一个结论:ReentrantLock 的 lock()并不响应中断。
    • 如果想要使用相应中断式锁可以使用lockInterruptibly()
  • 最终会回到acquire(1)中 执行 selfInterrupt()设置当前线程的中断标志位,如何处理交给用户自己来决定(是否相应中断等)
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    以独占模式获取,忽略中断。通过至少调用一次tryAcquire来实现,成功后返回。
    否则,线程将排队,可能会重复阻塞和取消阻塞,调用tryAcquire直到成功。
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

    static void selfInterrupt() {
        //设置中断标志位,并不会直接将线程停止
        Thread.currentThread().interrupt();
    }

思考2

以下这段逻辑不执行是否可以?也就是head的waitStatus还是-1

        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

个人理解:也是可以的,因为即使head waitStatus还是-1,只是在lock执行过程中如果执行到shouldParkAfterFailedAcquire方法时,直接就返回了true,这样就减少了一次当前节点尝试获取锁的机会,这也是这里设计的精妙之处。

6、unlock()方法流程图

未完持续更新,欢迎留言一起讨论!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值