Lock、AbstractQueuedSynchronizer

本文详细介绍了Java并发中的Lock接口及其内部实现AbstractQueuedSynchronizer(AQS)。AQS是一个模板类,用于构建锁和其他同步组件,包括独占锁的获取和释放、可中断和超时获取锁的机制。文章通过分析AQS的数据结构和核心方法,揭示了其同步队列的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Lock简介

Lock是并发访问共享资源的另一种方式,当需要使用chain lock这种时会很灵活,jdk文档中举了一个例子:(For example, some algorithms for traversing concurrently accessed data structures require the use of "hand-over-hand" or "chain locking": you acquire the lock of node A, then node B, then release A and acquire C, then release B and acquire D and so on.)

而Lock通常可以结合Condition来使用(通过lock.newCondition()方法获得),当使用Lock来替代Synchronized同步方法或者同步语句块时,Condition替代对象监视器方法(wait(),notify(),notifyAll())

二、AQS简介

AQS是一个抽象类,使用了模板设计模式,定义了一些抽象方法供子类重写,在AQS一些方法中会调用这些被子类重写的方法。

在文章初识Lock与AbstractQueuedSynchronizer(AQS)中对AQS进行了总结:

三、AQS详解 
1、排队区分为同步队列和条件队列。同步队列只有一个,条件队列可以有多个,只有获取锁后才能进入条件队列

同步队列的结构是怎样的呢?

查看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;

得知AQS的同步队列是由头结点和尾结点维护的,那么再看看Node里面的内容:

    static final class 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;
        /** 在同步队列等待 */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;
        /** 节点状态(SIGNAL、CANCELLED、CONDITION、PROPAGATE、0)*/
        volatile int waitStatus;
        /** 前驱节点 */
        volatile Node prev;
        /** 后继结点 */
        volatile Node next;
        /** 同步队列的下一个结点,或者是特殊值SHARED。因为同步队列只有在独占模式下使用,通过这个字段可以表明是否是共享模式 */
        Node nextWaiter;
        ...
    }

可以看到Node拥有前驱和后继,所以同步队列是一个双向队列,在AQS中管理同步队列的数据结构大致如下(摘自深入理解AbstractQueuedSynchronizer(AQS)):

2、独占锁

(1)独占锁获取锁

使用Lock的lock实际上会调用acquire方法。

    public final void acquire(int arg) {
        /** 尝试以独占的方式获取锁,获取成功则返回,否则将线程包装成结点(独占模式)并放入同步队列 */
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

① tryAcquire方法

    //Attempts to acquire in exclusive mode.(需要被子类覆盖)
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

② addWaiter方法

    /**
     * 创建入队结点
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     */
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        //采用尾插法
        //如果尾结点不为null,直接插入队尾
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //如果尾结点为null,此处会进行初始化入队的操作
        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;
            } else {//重复执行插入队尾的操作
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

③ acquireQueued方法

     /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @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.获得前继结点
                if (p == head && tryAcquire(arg)) {//2.如果前继结点是头结点,并且成功获取同步状态,即可以获得独占式锁
                    setHead(node);//3.成功后设置头结点
                    p.next = null; // 释放前继结点
                    failed = false;
                    return interrupted;
                }
                //4.获取锁失败尝试将该结点挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())//shouldParkAfterFailedAcquire返回true的话,通过parkAndCheckInterrupt阻塞线程
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);//如果失败,取消尝试获取
        }
    }
    /**
     * 获取锁失败的时候检查和更新状态.
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             *前继结点状态已经是signal了,当前继结点释放锁时会通知当前结点,所以可以安全地挂起了
             */
            return true;
        if (ws > 0) {
            /*
             * 前继结点可能由于超时或中断被取消了,需要重新找到状态不是被取消的结点
             * 该步骤如图1所示
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * 此时waitStatus只会是0或者PROPAGATE.表示需要signal状态但是暂时不挂起.
             * 使用CAS设置前继结点状态为Signal,外层死循环会重新调用                        
             * shouldParkAfterFailedAcquire(),直到compareAndSetWaitStatus设置成功,
             * 此时前继结点状态为signal,返回true,可以安心挂起了呀
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

    /**
     * Cancels an ongoing attempt to acquire.
     *
     * @param node the node
     */ 
     private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;

        node.thread = null;//1.将结点里包装的线程置空,node不再关联到任何线程

        // 2.向前找到一个有效的、没有被取消的前继结点
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        Node predNext = pred.next;//实际上就是node

        node.waitStatus = Node.CANCELLED;//3.将node的waitStatus置为CANCELLED

        //4.如果node是tail,更新tail为pred,并使pred.next指向null
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            //5. 如果node既不是tail,又不是head的后继节点(pred != head)
            //则将node的前继节点的waitStatus置为SIGNAL
            //并使node的前继节点指向node的后继节点(相当于将node从队列中删掉了)
            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 {
                //6. 如果node是head的后继节点,则直接唤醒node的后继节点
                unparkSuccessor(node);
            }
            node.next = node; // help GC 后继指向自己
        }
    }

如果是步骤6的情况 :node是head的后继节点,后继指向自己后,队列如下图所示,此时node被取消了,但还在队列中

下面对cancelAcquire的解析内容摘抄修改自(Java AbstractQueuedSynchronizer源码阅读3-cancelAcquire()作者:lzwang2)

cancelAcquire()调用了unparkSuccessor()
不过,unparkSuccessor()中并没有对队列做任何调整呀。
这次,cancelAcquire()对于出队这件事情可以说是啥都没干。
出队操作实际上是由unparkSuccessor()唤醒的线程执行的。
unparkSuccessor()会唤醒successor关联的线程(暂称为sthread),当sthread被调度并恢复执行后,将会实际执行出队操作。
现在需要搞清楚sthread是从什么地方恢复执行的呢?这要看sthread是在哪里被挂起的。在哪里跌倒的,就在哪里站起来。
本文开头在使用场景中,列出了调用cancelAcquire()的所有接口,也正是在这些接口中,线程将有可能被挂起。这些方法的代码结构类似,主体是一个for循环。这里以acquireQueued()为例,如下所示:
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;
}

sthread当初就是被parkAndCheckInterrupt()给挂起的,恢复执行时,也从此处开始重新执行。sthread将会重新执行for循环,执行到shouldParkAfterFailedAcquire()处。shouldParkAfterFailedAcquire()中将会调整successor的prev指针(同时也调整head的next指针),从而完成了node的出队操作。

此时变为下图:

 (2)独占锁释放锁

    /**
     * Releases in exclusive mode.  Implemented by unblocking one or
     * more threads if {@link #tryRelease} returns true.
     * This method can be used to implement method {@link Lock#unlock}.
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {//如果同步状态释放成功(tryRelease返回true)
            Node h = head;
            if (h != null && h.waitStatus != 0)//队列头结点不为空且状态不为0
                unparkSuccessor(h);//唤醒 h 的后继结点
            return true;
        }
        return false;
    }
    /** 尝试设置释放的状态 */
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
    /**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     */
    private void unparkSuccessor(Node node) {
        /*
         * 1.如果node的状态小于0,,(i.e., possibly needing signal),尝试使用CAS设置状态为0
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * 2.唤醒node的后继结点。如果后继结点被取消或者是空,从队尾开始遍历
         * 找到没有被取消的后继结点
         */
        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;
        }
        if (s != null)//如果存在没有被取消的结点
            LockSupport.unpark(s.thread);//唤醒结点线程
    }

每一次锁释放后就会唤醒队列中该节点的后继节点所引用的线程,验证了获得锁的过程是一个FIFO(先进先出)的过程。

(3)可中断式获取锁

调用lock.lockInterruptibly()实际上调用的是AQS的acquireInterruptibly

    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))//获取失败
            doAcquireInterruptibly(arg);
    }
    /**
     * Acquires in exclusive interruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //线程被中断时抛出异常
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

可以看到与 acquireQueued逻辑基本一致,只是parkAndCheckInterrupt返回true时,在acquireQueued通过一个布尔类型的标志interrupted = true 来表示中断,而doAcquireInterruptibly中断时直接抛出异常。

(4)超时等待获取锁

调用lock.tryLock(timeout,TimeUnit)实际上调用了AQS的tryAcquireNanos

    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }
    private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;//1. 根据超时时间和当前时间计算出截止时间
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {//2.成功获取,出队
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();//3.1 重新计算超时时间
                if (nanosTimeout <= 0L)//3.2 已经超时,直接返回false
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

 3、共享锁

(1)共享锁获取(该部分解析修改自深入浅出AQS之共享锁模式 作者:凌风郎少

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    /**
     * Acquires in shared uninterruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {//成功获取 等于0表示不用唤醒后继节点,大于0需要
                        setHeadAndPropagate(node, r);//设置头部
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    /**
     * 设置队列头, 以及检查是否有共享模式的后继结点在等待, if so propagating if either     
     * propagate > 0 or PROPAGATE status was set.
     *
     * @param node the node(成功获取锁的结点)
     * @param propagate the return value from a tryAcquireShared
     */
     private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below

        //注:这里是获取到锁之后的操作,不需要并发控制
        setHead(node);//设置新的头结点,即把当前获取到锁的节点设置为头节点
        /*
         * 1.propagate > 0 表示调用方指明了后继节点需要被唤醒
         * 2.头节点后面的节点需要被唤醒(waitStatus<0),不论是老的头结点还是新的头结点
         */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            //如果当前节点的后继节点是共享类型或者没有后继节点,则进行唤醒
            //这里可以理解为除非明确指明不需要唤醒(后继等待节点是独占类型),否则都要唤醒
            if (s == null || s.isShared())//只唤醒同样是共享模式的后继结点
                doReleaseShared();//详细查看下面释放锁的解析
        }
    }

 (2)共享锁释放(该部分解析修改自深入浅出AQS之共享锁模式 作者:凌风郎少

    //释放共享模式的锁
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    /**
     * Release action for shared mode -- signals successor and ensures
     * propagation. (Note: For exclusive mode, release just amounts
     * to calling unparkSuccessor of head if it needs signal.)
     */
    private void doReleaseShared() {
        for (;;) {
             //唤醒操作由头结点开始,注意这里的头节点已经是上面新设置的头结点了
            //其实就是唤醒上面新获取到共享锁的节点的后继节点
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                     //这里需要控制并发,因为入口有setHeadAndPropagate跟release两个,避免两次unpark
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
            //如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
             //如果头结点没有发生变化,表示设置完成,退出循环
            //如果头结点发生变化,比如说其他线程获取到了锁,为了使自己的唤醒动作可以传递,必须进行重试
            if (h == head)                   // loop if head changed
                break;
        }
    }

注:上面的setHeadAndPropagate()方法表示等待队列中的线程成功获取到共享锁,这时候它需要唤醒它后面的共享节点(如果有),但是当通过releaseShared()方法去释放一个共享锁的时候,接下来等待独占锁跟共享锁的线程都可以被唤醒进行尝试获取。

处于独占模式下时,其他线程试图获取该锁将无法取得成功。在共享模式下,多个线程获取某个锁可能(但不是一定)会获得成功,所以需要判断头结点是否发生了变化。 

4、总结

头节点可以认为就是当前占有锁资源的节点
A、独占模式
(1)不可中断:
I.获取锁
   ①线程先去尝试获取锁,如果获取失败后该线程被包装成节点Node,进入同步队列,如果同步队列尾结点不为空,将该节点设置为新的尾结点,否则进入同步队列的初始化。
   ②此时排队的线程仍未被挂起,如果这个节点是同步队列第一个节点,它会再次去尝试获取锁,获取失败的话会尝试将该节点挂起。判断如果该线程前继结点的状态为signal,则直接挂起,否则可能被挂起后前继结点不会唤醒它,所以用CAS设置前继结点状态为Signal(这期间线程不响应中断,只是设置一个中断的标志interrupted = true)
II.释放锁
    释放锁的顺序是从队头H开始的,如果H等待状态小于0,将H的等待状态设置为0;如果H不为空,获取头结点的直接后继结点(H.NEXT),如果该结点(H.NEXT)为空,则从队列尾部遍历寻找离H最近,并且不是取消状态的结点。通过LockSupport.unpark(s.thread)唤醒线程
(2)可中断:
 与不可中断类似,但在步骤2中尝试将线程挂起如果满足条件会响应中断并抛出异常
B、共享模式
    tryAcquireShared返回的不是boolean值,而是int。负数:表示获取失败;零值:表示当前结点获取成功, 但是后继结点不能再获取了;正数:表示当前结点获取成功, 并且后继结点同样可以获取成功
(1)获取锁
    如果获取成功,则将自己设置为头结点,等待状态如果是signal,此时唤醒后继结点中同样是共享模式的结点,将自己状态设置为0。等待状态如果是0(没有排队线程),则将自己状态设置为PROPAGATE,告知后面的线程可以来直接获取锁,如果是独占模式,会直接离开队列。
(2)释放锁
    释放成功会唤醒后继结点,如果自己状态为SIGNAL,则唤醒后继结点,如果不是,将自己状态设置为PROPAGATE

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值