如何体现AQS的价值,通过这些并发工具类

这篇文章主要通过并发包中的几个高级工具类来展示AQS的价值,具体AQS的详细解析,看另外一篇文章


除了ReentrantLock之外,还有另外一下同步组件用到了AQS,例如 java.util.concurrent.locks.ReentrantReadWriteLock读写锁、 java.util.concurrent.Semaphore信号量、 java.util.concurrent.CountDownLatch。我们再来看一看它们是如何使用AQS实现自己的能力。

AQS中的同步状态state在不同的类中被使用的方式不同。

ReentrantLock

在ReentrantLock被使用的方式是等于0的时候表示锁处于可获取状态,等于1的时候表示锁已经被获取,需要排队等待。

CountDownLatch

在CountDownLatch类中,state!=0表示倒计时还没有结束,此时调用await方法的线程将一直处于阻塞状态。因为在初始化CountDownLatch对象的时候,会设置一个初始值,每次调用countDown方法都会将state值减去1,直至减到0。我们来看一下几个关键方法

public void countDown() {
    sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

调用countDown方法后,会调用AQS中的模板方法releaseShared,模板方法会调用CountDownLatch中重写的protected修饰的tryReleaseShared方法。如果释放成功,也就是将state减到了0,那么回到AQS中调用doReleaseShared方法,来将同步队列中等待状态的线程唤醒,也就是最开始调用await方法获取锁失败的线程。下面是tryReleaseShared方法和doReleaseShared方法

protected boolean tryReleaseShared(int releases) {
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

看完了releaseShared方法,我们再来看看await方法,await调用AQS中的模板方法获取资源,模板方法调用CountDownLatch中实现的protected修饰的方法和排队等待方法。

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

其实也能够想到,countDown方法用来释放资源,那么await方法肯定是用来获取资源,所以调用的是acquireShared方法。如果获取失败,那么调用AQS中的doAcquireSharedInterruptibly方法来将当前线程加入同步队列进行等待。事实上也基本上会获取失败

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

从这里能够看出,只要state不是0,那么就会获取失败,因为开始初始化CountDownLatch时会设置state的值为一个大于0的整数,调用countDown方法时,才会将state减去1。所以开始调用await方法的线程会等待倒计时结束才能恢复,这也就是CountDownLatch对象被使用的场景:一个线程等待其他多个线程执行完任务才恢复继续执行。

Semaphore

Semaphore在使用时也会将state初始化为大于0的一个整数,这一点跟CountDownLatch类似。但是,Semaphore中这个值表示资源的个数,也就是并发数。当调用acquire方法时,会将资源数减1,当减到0之后,再调用acquire方法就会将当前线程阻塞。

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

acquire方法会调用AQS中的模板方法(前面已经展示过),这个模板方法会调用Semaphore自己重写的protected修饰的方法,我们可以看出,当资源数不够时,会返回负数,然后将当前线程加入同步队列中进行排队,并阻塞当前线程。

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

当调用release方法释放资源时,会调用AQS中的模板方法releaseShared,模板方法调用Semaphore中实现的方法tryReleaseShared来释放资源,其实就是把state的值加1。之后再调用AQS中的doReleaseShared方法来实现唤醒队列中等待的线程。

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next))
            return true;
    }
}

总结来说,信号量与重入锁都是实现控制并发的工具。信号量可以实现多个线程并发,也可以控制一个线程并发,只需要在初始化是将state设置为不同的整数。也就是说他能够实现重入锁实现的功能,而且功能更为强大。它们低层的实现都是通过AQS这一个类。

ReentrantReadWriteLock

读写锁低层也是用了AQS来进行实现。它与其他有所不同的是,它有两个同步状态。两个同步状态还是用一个state来进行表示。前面高16位表示读状态,低16为表示写状态。写锁的获取是独占式的类似于ReentrantLock,读锁的获取是共享式的,类似于Semaphore。详细的我就不再展开了,其实这些工具类都是自己实现了protected修饰的方法,而大量的方法,无论是共享的还是独占的,无论是模板的还是非模板的都是来自于AQS,可以通过阅读源码得知。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值