AQS之acquire方法

本文详细剖析了Java并发工具类中AQS的acquire方法,包括其独占式和共享式资源获取,以及tryAcquire、addWaiter、acquireQueued等核心方法的实现。在获取资源失败后,线程会被加入同步队列并阻塞,同时检查线程是否被中断。通过对AQS的深入理解,可以更好地掌握Java并发编程的底层原理。
摘要由CSDN通过智能技术生成

前言

JUC(java.util.concurrent)包下的很多并发同步工具类,大多基于AQS(AbstractQueuedSynchronizer 抽象队列同步器)实现加解锁的逻辑,其中acquire()尤为重要。

① acquire()是独占式地获取资源,acquireShared()是共享式地获取资源,如ReentrantReadWriteLock的writeLock和readLock;
② AQS采用模板方法模式实现acquire(),封装了线程获取资源失败后,进入同步队列并阻塞的逻辑。

三个核心方法

1. tryAcquire(int arg)
2. addWaiter()
3. acquireQueued(final Node node, int arg)

public final void acquire(int arg) {
    //尝试获取资源失败,且成功加入同步队列,则阻塞线程
    if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg))
        selfInterrupt();
}

1. tryAcquire(int arg)

① 尝试获取资源,留给子类重写,否则会抛出异常;
② 尝试成功则修改资源状态及拥有者,尝试失败则返回false。

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

2. addWaiter()

① 将当前线程封装成Node节点,并添加到同步队列尾部;
② 队列未初始化时,pred和tail都为null,tail为null时执行enq();
③ compareAndSetTail(pred, node)是采用cas线程安全地设置队列尾节点。

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
2.1 enq(final Node node)

① 通过cas自旋的方式将节点添加到队列尾部;
② tail为null表示同步队列未初始化,即没有虚拟head节点;
③ 所以在没有虚拟节点的情况下,先走if逻辑添加虚拟节点,再走else逻辑的。

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) {
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

3. acquireQueued(final Node node, int arg)

该方法是将获取资源失败的线程加入到同步队列中,并阻塞线程。
① 如果前置节点为头结点且成功获得资源,则将当前节点设置为头结点;
② 前置节点非头节点的线程,在获取资源失败后需要阻塞;
③ 阻塞线程并检查是否中断,中断则退出同步队列。

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;
            }
            // ②阻塞线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
3.1 shouldParkAfterFailedAcquire(Node pred, Node node)

① 判断前节点的ws是否大于0,大于0表示节点被取消(因中断等异常原因)
② 如果前节点被取消,则一直往前找,直到找到不被取消的节点
③ 将前节点的ws设置为SIGNAL,表示下一个节点需要被唤醒

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
3.2 parkAndCheckInterrupt()

① LockSupport调用UNSAFE类的unpark()阻塞当前线程

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

总结

并发工具类调用lock()方法进行加锁操作时,底层采用了acquire()进行资源获取。首先,会尝试获取资源(交给子类实现),获取资源失败则封装成Node节点,放入同步队列中并阻塞挂起,同时检测是否中断,中断则取消排队。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值