Java并发-AQS原理

AQS实现原理

AQS内部维护了一个同步状态state和一个CLH线程等待队列。当state > 0 时则代表当前对象锁已经被占有,其他线程来加锁时则会失败,加锁失败的线程会被堵塞并封装成Node节点插入到CLH等待队列中,等待其他获取锁的线程释放锁后再被唤醒。

image-20231103100547375

源码分析(以ReetrantLock为例)

UML图

image-20231103100558580

状态枚举

ReentrantLock状态state

  • state为0时,代表所有线程没有获取锁
  • state为1时,代表有线程获取到了锁
  • state > 1时,代表此线程重入了多少次锁

AQS节点的等待状态waitStatus

  • CANCELLED(1):取消状态,当线程不再希望获取锁时,设置为取消状态
  • SIGNAL(-1):待唤醒状态,当前节点的后继者处于等待状态,当前节点的线程如果释放或取消了同步状态,通知后继节点
  • CONDITION(-2):等待队列的等待状态,当调用signal()时,进入同步队列
  • PROPAGATE(-3):共享模式,同步状态的获取的可传播状态
  • 0:初始状态

获取锁

ReentrantLock#lock->ReentrantLock.Sync#lock->NonfairSync#lock

如果线程交替执行,不存在竞争时,是不会生成等待队列的, 这边等待队列时一种懒加载的处理,节省内存。

final void lock() {
    // 如果state=0说明还没有线程获取到锁, 设置1 且设置持有线程为当前线程
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // 获取锁
        acquire(1);
}

AbstractQueuedSynchronizer#acquire 获取锁

public final void acquire(int arg) {
    // 尝试获取锁,如果不成功将当前线程加入到等待队列中且中断线程, 如果成功则通过
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // 如果线程在park后是被中断唤醒的那么需要恢复中断标记
        selfInterrupt();
}

以非公平锁为例, 尝试获取锁
ReentrantLock.Sync#nonfairTryAcquire

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 如果如果state=0说明还没有线程获取到锁, 设置acquires 且设置持有线程为当前线程
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 如果state != 0, 但是当前线程持有锁, 那么state累加(可重入的实现原理)
    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;
}

AbstractQueuedSynchronizer#addWaiter 节点入队

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;
    // 尾节点存在时,将当前节点添加到尾节点 (尝试快速入队,竞争失败则走下面的自旋入队。注意:node 与 tail 相连非原子操作)
    if (pred != null) { 
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 节点自旋入队
    enq(node); 
    return node;
}

AbstractQueuedSynchronizer#enq 节点入队

private Node enq(final Node node) { 
    // 自旋直到将节点入队
    for (;;) {
        Node t = tail;
        // 尾节点为空时, 初始化一个dummy节点作为头尾节点
        if (t == null) { 
            if (compareAndSetHead(new Node()))
                tail = head;
        } else { 
            // 存在尾节点时, 将当前节点添加到尾节点 (注意:node 与 tail 相连非原子操作)
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

AbstractQueuedSynchronizer#acquireQueued 从队列中获取锁资源

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;
            }
            // 如果前序节点非头结点或者获取锁失败
            // 判断锁是否应该停止自旋进入阻塞状态
            // 如果前序节点等待状态是SIGNAL,那么可以开始中断当前线程,进行排队等待【wait】
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        // 只有异常情况才会触发cancelAcquire方法取消该节点
        if (failed)
            cancelAcquire(node);
    }
}

AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire 将前序节点waitStatus置为SIGNAL , 返回true代表当前线程可以被阻塞了, 然后等待前序节点唤醒当前线程。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    // 如果前序节点等待状态是SIGNAL, 那么当前线程需要等待前序节点的unpark唤醒,直接返回true。
    if (ws == Node.SIGNAL) 
        return true;
    if (ws > 0) { 
        // 如果前序节点等待状态>0 , 也就是取消状态 那么移除所有取消的节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 其他设置前序节点为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 
    }
    return false;
}

AbstractQueuedSynchronizer#parkAndCheckInterrupt

private final boolean parkAndCheckInterrupt() {
    // 阻塞当前线程,等待前序节点来释放该线程
    LockSupport.park(this);
    // 返回线程被唤醒后的中断状态(如果中断那么在获得锁后需要恢复中断标记)
    return Thread.interrupted();
}

一起看返回true时的AbstractQueuedSynchronizer#selfInterrupt 设置线程中断方法

private static void selfInterrupt() {
    // 设置线程中断
    Thread.currentThread().interrupt();
}

注意:关于中断方法, 此处使用Thread.interrupted()返回是否在park期间被中断过。 这里需要清除中断状态, 否则如果没有获取到锁再次进来走到park方法时将无法再次阻塞(park方法调用时如果interrupt状态true那么会直接跳过)。这也是为什么不直接用isInterrupted方法的原因。 基于此在获取到锁之后要调用selfInterrupt进行回复中断标记。

AbstractQueuedSynchronizer#cancelAcquire

取消节点, 主要做三个操作

  1. 当前节点设置取消状态;
  2. 将当前取消节点的前序非取消节点和后序非取消节点连接起来;
  3. 如果前序节点是队首,那么需要唤醒后续节点。
private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    if (node == null)
        return;

    node.thread = null;

    // Skip cancelled predecessors
    Node pred = node.prev;
    // 跳过所有的取消节点
    while (pred.waitStatus > 0) 
        node.prev = pred = pred.prev;

    Node predNext = pred.next;

    // 当前节点设置为取消状态
    node.waitStatus = Node.CANCELLED; 

    // 如果当前节点是尾节点那么直接去除当前节点
    if (node == tail && compareAndSetTail(node, pred)) { 
        compareAndSetNext(pred, predNext, null);
    } else {
        int ws;
        // 前序节点不是头结点 
        // 且 前序节点状态为-1待唤醒状态 或者 (状态<0 且 cas 设置成-1) 
        // 且 线程不为空(取消状态)
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            // 当前节点next不为空且非取消状态时,将前序节点的next指向当前节点的next
            Node next = node.next;
            if (next != null && next.waitStatus <= 0) 
                compareAndSetNext(pred, predNext, next);
        } else { 
            // 前序节点是队首,那么需要唤醒后继节点
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}

AbstractQueuedSynchronizer#unparkSuccessor 唤醒后继节点

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    // 如果后继节点s为空或者取消,那么从尾节点往前找一直找到最前面的非取消状态的节点设置为s
    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);
}

注意: 此处唤醒后继节点时是从尾节点开始往前去找,因为前面节点入队时node 与 tail 相连非原子操作,存在pred指针已连接但前节点next指针未连接节点为空的情况。

释放锁

ReentrantLock#unlock

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

AbstractQueuedSynchronizer#release

public final boolean release(int arg) {
    // 尝试释放锁
    if (tryRelease(arg)) {
        Node h = head;
        // 全部释放成功后,如果头节点不为空 且状态不为初始0 则唤醒后继节点
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

ReentrantLock.Sync#tryRelease 尝试释放锁

protected final boolean tryRelease(int releases) {
    // 计算锁被占用的次数-释放的次数, 如果为0 则代表锁全部释放完
    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;
}

AbstractQueuedSynchronizer#unparkSuccessor 唤醒后继节点在上面加锁的时候已经分析过,不再赘述。

公平锁与非公平锁的区别

区别主要公平锁尝试获取锁的方式比非公平锁多了一个判断是否有正在排队的节点。

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 如果如果state=0说明还没有线程获取到锁, CLH队列是否存在排队节点 且设置acquires、持有线程为当前线程
    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;
}

AbstractQueuedSynchronizer#hasQueuedPredecessors判断是否有排队节点

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.
    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());
}

【本文完】

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值