AQS 锁的设计框架

本文详细解析了ReentrantLock的内部实现,包括其如何基于AQS(AbstractQueuedSynchronizer)管理锁状态,以及线程在获取锁失败后如何进入同步队列等待,同时介绍了Condition的使用,展示了await()和signal()方法的工作原理。
摘要由CSDN通过智能技术生成

        

目录

加锁

锁释放

 Condition 等待队列 


        和synchronized一样, ReentrantLock 是一个很常见常用的锁,它的底层使用了AQS ,AQS是jdk里线程同步,锁的框架。AQS比synchronized要复杂很多,AQS是一把锁,它有一个同步队列和多个等待队列

        当一个线程抢锁失败时,该线程会被封装为一个 Node,加入到同步队列中去,当线程调用await 方法时,它就进入到一个等待队列里面排队,当 signal 方法被触发时,它会从等待队列移动到锁的同步队列排队。

当然,这个Node中会有一个 waitStatus 变量来表示该线程的状态是什么,处于哪个队列。Node代码里通过CANCELLED,SIGNAL,CONDITION等类变量描述了这一点。等待队列使用Condition接口,它复用了Node对象,AQS的等待队列和synchronized的等待队列没多少区别,Condition接口的await(),signal()和Object的wait(),notify() 差不多。


public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
    static final class Node {
        static final Node SHARED = new Node();
        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;
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }
    private transient volatile Node head;
    private transient volatile Node tail;
    private volatile int state;
}

一个ReentrantLock 的demo


public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        new Thread(() -> {
            try{
                lock.lock();
                System.out.println("模拟复杂的业务逻辑,3秒后完成");
                try{
                    Thread.sleep(3*1000);
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("线程A 已执行完毕,释放资源");
            }finally {
                lock.unlock();
            }
        },"线程A").start();
        new Thread(() -> {
            try{
                System.out.println("线程B进行抢占");
                lock.lock();
            }finally {
                lock.unlock();
            }
            System.out.println("线程B 已执行完毕,释放资源");
        },"线程B").start();
    }

当我们使用lock.lock()时,就会去抢锁。

ReentrantLock有公平锁和非公平锁的实现,默认是非公平锁。


public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

非公平锁这样加锁,它想把AbstractQueuedSynchronizer的state 变量通过CAS的方式从0修改为1。


final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

如果修改成功,就会获取锁,失败就走acquire(1)分支,ReentrantLock没有覆写acquire(),而是继续沿用了AQS的方法,所以AQS就是锁的框架 。


public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire(),addWaiter(),acquireQueued()都是模板方法,父类定义了流程顺序,子类根据需要可以覆写。

在ReentrantLock的非公平实现 NonfairSync 中,再次通过CAS,可重入的方式试探还能否加锁。


final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

加锁

失败后进入addWaiter(Node.EXCLUSIVE),其中static final Node EXCLUSIVE = null。另外AbstractQueuedSynchronizer的tail,pred两个节点初始时肯定都为null。 主要,头结点head,尾结点tail是AQS的变量,而前一个节点pred,后一个节点next是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
    // tail 是 AQS的tail变量
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        // compareAndSetTail(期望值,更新值),后续也如此
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

自旋加入,for循环+CAS,就是无锁自旋。如果尾结点为空,进入enq(node),会加入一个空节点作为头结点,第一个加入的Node为尾结点,这个哨兵节点可以理解为是初始化的一部分。变成这样:

所以第一次for循环会新建哨兵节点,第二次进入for循环才会把节点加入进链表。在for循环里,拷贝一份tail变量命名为t,然后后续都使用t进行操作,最后更新tail (tail = head;),有点类似于写时复制。


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;
            }
        }
    }
}

此时线程B抢锁失败,进入到队列。

AQS#acquireQueued中,又是for循环包裹的代码,自旋,每个还未被阻塞的 Node 节点都监听自己的前序节点,如果前序节点是同步队列的 head 节点,就尝试抢锁。


// arg 为 1
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;
            }    
            // 判断是否要阻塞掉抢锁失败的当前线程,不然又进入到 for 循环自旋
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

node.predecessor()会获取到node节点的前一个节点,如果前一个节点pred就是AQS的头结点head,则该Node对应的线程会再次抢锁,抢锁成功就会把AQS的头结点head设置为node。

抢锁失败进入shouldParkAfterFailedAcquire()方法,判断是否需要阻塞掉该线程。


// pred是头结点,node是线程B    
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        // waitStatus没找到赋值的地方,默认为0,可以debug验证
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL) //Node.SIGNAL=-1
            // 第二次for循环走这里,才会开始阻塞线程B
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // 第一次for循环走这,设置头节点的waitStatus为-1
            // 第一次for循环算是环境初始化的一部分吧,比较绕
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

static final int CANCELLED =  1;
static final int SIGNAL    = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;

parkAndCheckInterrupt() ,


private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

当线程A释放锁时,lock.unlock()


// arg 为 1
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        // 获取AQS的头结点
        Node h = head;
        // 头结点不为空且头结点的waitStatus不为0,说明有线程被阻塞了
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

锁释放

ReentrantLock#tryRelease() ,对 state进行减1操作,如果本线程不是持有锁的线程,直接报错。


protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

unparkSuccessor 会先把头结点head的waitStatus设置为0,


//node是头结点head
private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        // 默认同步队列节点的状态是 0 
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next; // s 是线程B,不为 null
        // s.waitStatus > 0 的情况只有 CANCELLED 状态
        if (s == null || s.waitStatus > 0) { // waitStatus==0
            s = null;
            // 从尾结点开始往前遍历,
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        // 唤醒 s 线程去抢锁,让 s 线程继续在 acquireQueued 方法里的 for 循环执行
        if (s != null) 
            LockSupport.unpark(s.thread);
    }

线程B被唤醒了。线程B会唤醒后会做什么呢?抢锁,其实线程B一直处于acquireQueued()方法的for循环中的,只不过它有段时间被迫阻塞了而已,被唤醒后还会一直抢锁。


// arg 为 1
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;
            }
            // 抢锁线程已进入同步队列,park 阻塞掉
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

 Condition 等待队列 

前面主要使用了AQS的同步队列,还有等待队列。不过等待队列一般不常用。

下面是一个使用Condition的demo。


public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        
        new Thread(() -> {
            try{
                lock.lock();
                System.out.println("线程A 抢到锁,模拟复杂的业务逻辑,3秒后完成");
                try{
                    Thread.sleep(3*1000);
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("线程A 已执行中止,进入等待队列");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("线程A 从等待队列出来,继续执行,执行完毕");
            }finally {
                lock.unlock();
            }
        },"线程A").start();
        new Thread(() -> {
            try{
                System.out.println("线程B 进行抢占");
                lock.lock();
                condition.signal();
            }finally {
                lock.unlock();
            }
            System.out.println("线程B 已执行完毕,释放资源");
        },"线程B").start();
    }

lock.newCondition() 返回一个 ConditionObject 。


public class ConditionObject implements Condition, java.io.Serializable {
    private static final long serialVersionUID = 1173984872572414699L;
    private transient Node firstWaiter;
    private transient Node lastWaiter;
    private static final int REINTERRUPT =  1; 
    private static final int THROW_IE    = -1;
}

condition.await()


public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    // 当前持有锁的线程已经加入到等待队列中后,就要释放锁了
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 判断节点是否在同步队列里,如果执行了 signal() 会导致Node线程转移到同步队列,退出循环
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this); 
        // 中断可以退出循环
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // acquireQueued() 开始抢锁,只有抢锁成功它才会返回true,如果之前有过中断,则再次中断。
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE) // THROW_IE=-1
        interruptMode = REINTERRUPT;  // REINTERRUPT=1
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)  // 处理中断
        reportInterruptAfterWait(interruptMode);
}
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        // 减少无用的锁竞争
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}
private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();
}    
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

unlinkCancelledWaiters()消除所有状态不是CONDITION的节点,把trail 变量理解为prev 就懂了。


private void unlinkCancelledWaiters() {
    Node t = firstWaiter;
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;
        if (t.waitStatus != Node.CONDITION) {
            t.nextWaiter = null;
            if (trail == null)
                firstWaiter = next;
            else
                trail.nextWaiter = next;
            if (next == null)
                lastWaiter = trail;
        }
        else
            trail = t;
        t = next;
    }
}

fullyRelease()方法释放持有的锁


final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}
// 同 同步队列释放锁一样
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

isOnSyncQueue() 判断节点是否在同步队列里。


final boolean isOnSyncQueue(Node node) {
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    if (node.next != null) // If has successor, it must be on queue
        return true;
    return findNodeFromTail(node);
}
private boolean findNodeFromTail(Node node) {
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
}

checkInterruptWhileWaiting() 会在Node进入等待队列时检查是否有中断产生。


private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}
// 线程被唤醒可能是因为被中断,也可能是因为其他线程执行了signal(),在执行signal()时,会设置Node等待状态值为0
final boolean transferAfterCancelledWait(Node node) {
    // 尝试把Node节点状态从 CONDITION 修改为0,修改成功就加入同步队列。如果原先Node的状态为CONDITION,说明不是signal()操作,而是中断唤醒了。
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        enq(node);
        return true;
    } 
    // 已经执行了signal()操作,直接让出cpu即可。
    while (!isOnSyncQueue(node))
        // yield() 会让当前线程让出cpu
        Thread.yield();
    return false;
}

signal()方法会把等待队列的第一个节点加入到同步队列中去。


public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}
    final boolean transferForSignal(Node node) {
        // If cannot change waitStatus, the node has been cancelled.
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        // 把节点加入到同步队列
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }


总结: state是锁的控制变量,抢锁失败就进入一个同步队列,这个同步队列的头节点就一个哨兵节点,起到监控整个队列的左右,当线程抢锁失败,会自旋两三次后进入这个同步队列并被阻塞,当锁释放时,会唤醒哨兵节点之后的第一个非空节点去抢锁。 AQS作为锁的框架,ReentrantLock只使用到了AQS的一部分,像其他的锁,其他的同步类则使用了AQS的其他部分。

未完待续。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值