AbstractQueuedSynchronizer(AQS)源码分析——独占锁(ReentrantLock)

AQS是什么?

AQS是Java中实现阻塞和唤醒线程的最基础类。全称 AbstractQueuedSynchronizer(同步队列),它是一个同步工具也是 Lock 用来实现线程同步的核心组件。而其中也是基于CAS原理比较替换实现添加和移除结点。

从使用层面来说,AQS 的功能分为两种:

  • 独占锁:每次只能有一个线程持有锁,比如ReentrantLock就是以独占方式实现的互斥锁。

  • 共享锁:允许多个线程同时获取锁,并发访问共享资源,比如ReentrantReadWriteLock读写锁

PS:本文中只讨论独占锁的源码实现

独占锁是什么?

  • 当多个线程一起获取锁的时候,只有一个线程能获取到锁,其他线程必须在当前位置阻塞等待。
  • 释放锁的功能。获取锁的线程释放锁资源,而且还必须能唤醒正在等待锁资源的一个线程。

首先了解AQS中的重要内部类——Node

在此只列出Node中重点属性。需要了解好这些属性的作用,才能更好理解之后的源码分析。

static final class Node {
     // 共享模式的标记
     static final Node SHARED = new Node();
     // 独占模式的标记
     static final Node EXCLUSIVE = null;

     // waitStatus变量的值,标志着线程被取消
     static final int CANCELLED =  1;
     // waitStatus变量的值,标志着后继线程(即队列中此节点之后的节点)需要被阻塞.(用于独占锁)
     static final int SIGNAL    = -1;
     // waitStatus变量的值,标志着线程在Condition条件上等待阻塞.(用于Condition的await等待)
     static final int CONDITION = -2;
     // waitStatus变量的值,标志着下一个acquireShared方法线程应该被允许。(用于共享锁)
     static final int PROPAGATE = -3;

     // 标记着当前节点的状态,默认状态是0, 小于0的状态都是有特殊作用,大于0的状态表示已取消
     volatile int waitStatus;

     // prev和next实现一个双向链表
     volatile Node prev;
     volatile Node next;

     // 该节点拥有的线程
     volatile Thread thread;
}

在此进一步说明waitStatus属性:

  • 当waitStatus = CANCELLED = 1:

    表示Node所代表的当前线程已经取消了排队,即放弃获取锁了。

  • 当waitStatus = SIGNAL = -1:

    它不是表征当前节点的状态,而是当前节点的下一个节点的状态。
    (当一个节点的waitStatus被置为SIGNAL,就说明它的下一个节点(即它的后继节点)已经被挂起了(或者马上就要被挂起了),因此在当前节点释放了锁或者放弃获取锁时,如果它的waitStatus属性为SIGNAL,它还要完成一个额外的操作——唤醒它的后继节点。)

  • 当waitStatus = CONDITION = -2:

    表示这个Node在条件队列中,因为等待某个条件而被阻塞。

  • 当waitStatus = PROPAGATE = -3:

    使用在共享模式头Node有可能处于这种状态, 表示锁的下一次获取可以无条件传播

AQS就是为了应对线程的阻塞和唤醒,在代码中分别对应acquire()和release()方法。因此接下来的主要内容便是对这两个方法的源码进行讲解分析。

获取锁方法acquire()

public final void acquire(int arg) {
    if (!tryAcquire(arg) && // 尝试获取锁
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 获取锁失败,阻塞线程
        selfInterrupt();
}
  • tryAcquire()方法尝试获取锁:

基于tryAcquire()方法有两种实现,一种是公平锁,另一种是非公平锁。因为Java中默认使用的是非公平锁,因此在本文的源码分析都是基于非公平锁。

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires); // 基于非公平锁的尝试获取锁
}
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) { // state为0说明该锁没有被线程占用。采用CAS操作对state加1,并设置当前线程为占用锁线程。说明获取锁成功,并返回true
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) { // state不为0,说明锁已被线程占用。如果占用锁线程为当前线程,则对state加1,并返回true(可重入机制)
        int nextc = c + acquires;
        if (nextc < 0) 
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false; // 否则返回false
}

如果获取锁成功返回true,则线程到此结束。如果获取锁失败返回false,则继续往下分析:

  • addWaiter()方法添加等待节点进行队列:
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode); // 基于当前线程和独占锁模式新建结点node
    Node pred = tail;
    if (pred != null) { // 尾结点不为空,则将node节点加入队列末尾
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node); // 尾结点为空,则初始化尾结点
    return node;
}

private Node enq(final Node node) {
    for (;;) { // 自旋
        Node t = tail;
        if (t == null) { // 尾结点为空,则初始化头尾节点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else { // 尾结点不为空,则添加node节点进行队列末尾。注意,此处构造了一个空的头结点。
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
  • acquireQueued()方法采用自旋方式,获取锁:
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) { // 自旋
            final Node p = node.predecessor(); // 获取node的前任结点p
            if (p == head && tryAcquire(arg)) { // p结点为头结点,并且尝试获取锁成功
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted; // 自旋直到获取到锁
            }
            if (shouldParkAfterFailedAcquire(p, node) && // 检查并更新结点状态
                parkAndCheckInterrupt()) // 阻塞线程
                interrupted = true;
        }
    } finally {
        if (failed)
            // failed为true,表示发生异常,非正常退出
            // 则将node节点的状态设置成CANCELLED,表示node节点所在线程已取消,不需要唤醒了
            cancelAcquire(node); 
    }
}
  • shouldParkAfterFailedAcquire()方法检查更新节点状态,并决定是否需要阻塞当前线程:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL) // node节点的前任节点的waitStatus属性为SIGNAL,说明当前节点中包含的线程应该等待。返回true,阻塞线程
        return true;
    if (ws > 0) { // waitStatus>0说明为CANCEL,则前任节点处于取消状态。
        do { // 向前一直找到未取消的节点为止
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
            // 此处waitStatus必为0或者PROPAGATE,不可能是CONDITION状态
            // CONDITION(这个是特殊状态,只在condition列表中节点中存在,独占锁队列中不存在这个状态的节点)
            // 将前任结点的状态设置成SIGNAL,这样在下一次循环时,就是直接阻塞当前线程
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false; // 返回false,不阻塞线程
}
  • parkAndCheckInterrupt()方法阻塞线程:
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this); // 阻塞当前线程
    return Thread.interrupted(); // 当前线程被唤醒后,返回当前线程中断状态
}

到此,AQS的acquire()方法执行的整个过程分析和源码讲解结束。

释放锁方法release()

public final boolean release(int arg) {
    if (tryRelease(arg)) { // 尝试释放锁
        Node h = head;
        if (h != null && h.waitStatus != 0) // 头结点不为空,而且waitStatus属性不为0。说明很可能存在等待结点需要进行唤醒
            unparkSuccessor(h); // 唤醒等待结点
        return true;
    }
    return false;
}
  • tryRelease()方法尝试释放锁:
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread()) // 占有锁线程不是当前线程,则抛异常
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) { // state为0,说明当前线程已经没有占有该锁
        free = true;
        setExclusiveOwnerThread(null); // 设置占有锁线程为null。即该锁自由了,不被任何线程占用
    }
    setState(c);
    return free; // 返回释放锁结果
}
  • unparkSuccessor()方法唤醒等待结点:
    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next; // 传入的node为空的头结点,因此需要找到后继节点才是真正的等待结点。
        if (s == null || s.waitStatus > 0) { // 等待结点为空,或者waitStatus为CANCEL,则从队尾开始往回找,找到waitStatus不为CANCEL的等待结点。即找到未取消的结点为止。
            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);
    }

到此,AQS的release()方法执行的整个过程分析和源码讲解结束。

最后,AQS的独占锁模式的获取、释放锁的源码讲解分析结束。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值