AbstractQueuedSynchronizer 源码学习基于 JDK 1.8

概览

AbstractQueuedSynchronizer 提供原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。

使用设计模式——模板方法,子类只需要实现很少几个接口,就可以定一个锁同步类,完成独占锁,共享锁的逻辑。

参考美团点评技术团队编写的:从ReentrantLock的实现看AQS的原理及应用 加上自己的理解来加深对 AQS 的理解。

本文暂时只介绍 AQS 独占加锁与解锁过程实现

ReentrantLock 与 AQS 关联

根据源码可知,ReentrantLock 的非公平锁加锁,只用实现 tryAcquire 获取锁的逻辑,就可以完成非公平的独占锁获取。其中获取锁失败进行等待的逻辑 AQS 已经帮我们实现。
在这里插入图片描述

1. 独占加锁 acquire 实现

// AQS acquire 方法
public final void acquire(int arg) {
	// 首先调用子类 tryAcquire 去获取锁,若获取锁失败,才执行后面的 addWaiter acquireQueued 逻辑
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt(); // 获取锁成功,当前线程中断
}

主要方法:

  • tryAcuire,子类重写获取锁的逻辑
  • addWaiter,添加节点到队列末尾
    • enq ,入队以及初始化队列
  • acquireQueued,返回 node 在等待过程中是否被中断过
    • shouldParkAfterFailedAcquire,阻塞获取锁失败的线程,删除队列中状态为取消的节点
    • parkAndCheckInterrupt,真正的阻塞办法
    • cancelAcquire,获取锁过程中发生了异常

1.1 tryAcquire

子类复写 tryAcquire 方法,里面实现获取锁逻辑,获取成功返回 true,获取失败返回 false。

参考 ReentranLock 灯同步类的实现,主要逻辑是 setState ,并将当前线程标记为已获取到该锁

1.2 addWaiter

将节点插入到队列尾部,若队列为初始化,先进行初始化

// 将 EXCLUSIVE 类型的 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;  // 尾节点
    if (pred != null) {  // 尾节点不为空时,将 node 插入到队列尾部
        node.prev = pred; // 队列是双向的,将 node 的前置指针指向 pred
        if (compareAndSetTail(pred, node)) {  // 这个方法就是将 tailOffset 的值与 pred 进行比较,若相同,将 tailOffset 的值设为 node,即将 tail 指向 node。
            pred.next = node;  // pred.next 指向 node  
            return node;  // 返回刚刚添加的 node
        }
    }
    // 尾节点为空(队列中没有元素,需要初始化)
    // 或者 compareAndSetTail 失败(tailOffset 处的值不是 pred,被其它线程修改,导致 tail 指向的节点和 pred 指向的节点不同)
    enq(node); // 死循环方式 插入到队列中
    return node;  // 返回刚刚添加的 node
}

// 插入节点到队列
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        // 尾指针为 null
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))  // 队列初始化
                tail = head; // ,head 和 tail 指向同一处,进行下一次循环
        } else {
        	// 队列中有元素
            node.prev = t;   // node 的前置指针指向 tail
            if (compareAndSetTail(t, node)) {  // 若 tailOffset 指向的值等于 t,将 tail 处的值改为 node
                t.next = node;  // 队列是双向的,将后置指针指向 node
                return t;  // 返回插入队列前 队列中的最后一个节点
            }
        }
    }
}

1.3 acquireQueued

循环从队列中查询 node 节点能否获取锁,直到线程获取成功或者被中断

// node 是刚刚插入队列结尾的节点,死循环去判断 node 是否获取到锁,返回是否被中断
final boolean acquireQueued(final Node node, int arg) {
	// 获取失败标志位
    boolean failed = true;
    try {
    	// 是否被中断
        boolean interrupted = false;
        // 死循环,直到获取锁成功
        for (;;) {  
        	// 刚刚插入节点 node 的前置节点
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) { // 若前置节点是头节点(头节点里面啥都没有),并且当前 Node 获取锁成功
                setHead(node);  // head 指针后移指向 node,并将 node 属性置为 null
                p.next = null; // help GC
                failed = false;
                return interrupted; 
            }
            // 上面条件不满足:p 不是头节点 或者 node 没有获取到锁
            // shouldParkAfterFailedAcquire 若锁获取失败,返回线程是否需要被阻塞
            // parkAndCheckInterrupt 阻塞当前线程(for 循环卡住),并返回当前线程是否被中断
            // 这个方法块的意思:循环判断 node 是否需要被阻塞,并进行阻塞直到被唤醒
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())  
                // 被中断过
                interrupted = true;
        }
    } finally {
    	// 出现不正常情况,取消获取锁
        if (failed)
            cancelAcquire(node);  // 后面讲
    }
}

1.3.1 shouldParkAfterFailedAcquire

// waitStatus 状态列举:
static final int CANCELLED =  1; // 取消状态
static final int SIGNAL    = -1;  // 后继线程需要解锁
static final int CONDITION = -2; // 等待某个条件的状态
static final int PROPAGATE = -3;  // 获取共享锁传播状态 

// 根据前驱节点 pred 判断 node 是否需要被阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	// 
    int ws = pred.waitStatus;
    // 前驱节点处于 唤醒状态,
    if (ws == Node.SIGNAL)
        return true;  // 那么返回需要被阻塞,被唤醒下面说
    // 若前驱节点处于取消状态,移除队列中 node 前面处于取消状态的节点
    if (ws > 0) {
        do {
        	// 往前遍历,直到找到队列中,节点不是 取消状态 的节点,将 node 的前置指向指向这个节点。
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        // 取消状态的节点,就从队列中移除了
        pred.next = node;
    } else {
    	// 将前置节点的 waitStatus 设为 SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    // 返回不需要被阻塞
    return false;
}

1.3.2 parkAndCheckInterrupt

// 挂起当前线程,并返回当前线程的中断状态
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

1.3.3 cancelAcquire

private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    if (node == null)
        return;
    node.thread = null;
    Node pred = node.prev;
    while (pred.waitStatus > 0) // 往前遍历,跳过 waitStatus = CANCELLED 的节点
        node.prev = pred = pred.prev;  
    Node predNext = pred.next;  // 删除中间 waitStatus = CANCELLED 的节点,pred.next 指向 node
    // 将 node 状态改为 CANCELLED
    node.waitStatus = Node.CANCELLED;
    // 如果 node 是尾节点,直接删除末尾的 node
    if (node == tail && compareAndSetTail(node, pred)) {
    	// pred 指向 predNext,predNext 为空节点
        compareAndSetNext(pred, predNext, null);
    } else {
        int ws;
        // 当 pred 不是头节点,并且 pred.waitStatus == SIGNAL 时
        // 或者 pred 不是头节点,并且 pred.waitStatus 不是 CANCELLED 状态,将 pred.waitStatus 改为 SIGNAL
        // 以上两种情况出现时,将 pred.next 指向 node.next。即删除 pred 指向 node 的指针,直接指向 node.next 
        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 {
        	// 上面条件不满足,即:
        	// pred 是头节点
        	// pred 状态为 CANCELLED 
        	// pred.thread == null
        	// 以上三种情况出现时,唤醒 node 后面阻塞的线程
            unparkSuccessor(node);
        }
        node.next = node; // help GC
    }
}

// 唤醒 node 后面的线程
private void unparkSuccessor(Node node) {

    int ws = node.waitStatus;
    if (ws < 0) // 不是 CANCELLED 状态时,把 node.waitStatus 设为 0
        compareAndSetWaitStatus(node, ws, 0);
   
    Node s = node.next;
    // 若 node 的后继节点为空,或者后继节点时 CANCELLED
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 从尾节点开始往前遍历,找到队列中从左往右第一个不是 CANCELLED 状态的节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
    	// 唤醒该节点
        LockSupport.unpark(s.thread);
}
  • 从尾节点开始往前遍历,找到第一个非 CANCELLED 节点的原因

addWaiter 方法节点入队 node.prev = pred; compareAndSetTail(pred, node) 不是原子操作,所以 pre 指针是完整的

其次,删除 CANCELLED 节点时,先删除的时 NEXT,Prev 没有改,所以可以完整遍历整个队列

1.4 acquire 方法总结

acquire 方法的总体思路:

  1. tryAcquire 子类实现获取锁逻辑,获取成功,方法结束
  2. 获取失败,通过 addWaiter 加入到队列结尾,并初始化未初始化的队列
  3. acquireQueued 循环判断 node 节点是否能获取到锁,里面做了几件事:
    • 当 node 节点前置节点是 head 并且再次尝试获取锁成功,直接返回
    • 判断获取失败的 node 节点是否需要被阻塞,里面会去删除队列中 waitstatus 为 CANCELLED 状态的节点,并返回线程的中断状态,若被阻塞,acquireQueued 中的 for 循环将被卡住,等待被唤醒
    • 获取锁期间发生异常,将队列中的 node 删除
  4. 跳出 acquireQueued 循环的条件是 node 前置节点是 head 并且获取到锁,否则进行阻塞等待
    在这里插入图片描述

2. 独占解锁实现

释放锁,调用 子类重写的 tryRelease 方法,若解锁成功,将唤醒 head 后面的 node

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        // 当头指针已经初始化
        // waitStatus == 0,表示后继节点正在运行中,不需要唤醒
        // waitStatus < 0,表示节点状态不是 CANCELLED,那么需要被唤醒
        
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

2.1 tryRelease

ReentranLock tryRelease 释放锁实现,公平锁和非公平锁都一样

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;
        // 将当前锁的所占线程置为 null
        setExclusiveOwnerThread(null);
    }
    // 更新 state
    setState(c);
    return free;

2.2 unparkSuccessor
unparkSuccessor 在上面 1.3.3 有介绍

3. 可中断加锁实现

逻辑基本与 acquire 一致,唯一不同的地方就是阻塞等待获取锁过程中,如果被中断,直接抛出异常。

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    // 如果当前线程被中断,直接抛出    
    if (Thread.interrupted())
        throw new InterruptedException();
    // 子类获取锁失败    
    if (!tryAcquire(arg))
    	// 执行具体的可中断的加锁逻辑
        doAcquireInterruptibly(arg);
}

// 这个方法和 acquireQueued 基本一样,唯一不同的就是等待过程中,线程被中断直接抛出(parkAndCheckInterrupt 返回 true)
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())
                // 与 acquireQueued 不同点
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

4. 超时等待式获取锁

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);      // 子类获取锁成功 或者 doAcquireNanos 获取成功
}

// 一个时间阈值,若时间小于该值,不阻塞当前线程而是继续获取,提高响应速度
static final long spinForTimeoutThreshold = 1000L;

private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    // 超时时间点    
    final long deadline = System.nanoTime() + nanosTimeout;
    // 添加到队列尾部
    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 true;
            }
            // 计算与截止时间的差距
            nanosTimeout = deadline - System.nanoTime();
            // 已经超时,直接返回 false
            if (nanosTimeout <= 0L)
                return false;
            // 阻塞获取锁并且差距要大于 1000,
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                // 指定阻塞指定时间
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值