如何体现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,可以通过阅读源码得知。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AQS(AbstractQueuedSynchronizer)是Java并发编程中的一个重要类,它可以理解为抽象的队列同步器。AQS提供了一种基于FIFO队列的同步机制,用于实现各种同步器,如ReentrantLockCountDownLatchSemaphore等。 AQS的核心思想是使用一个volatile的int类型变量state来表示同步状态,通过CAS(Compare and Swap)操作来实现对state的原子更新。AQS内部维护了一个双向链表,用于保存等待获取同步状态的线程。 AQS的具体实现包括以下几个方面: 1. 内部属性:AQS内部有两个重要的属性,一个是head,表示队列的头节点;另一个是tail,表示队列的尾节点。 2. 入队操作:AQS的入队操作是通过enq方法实现的。在入队操作中,首先判断队列是否为空,如果为空,则需要初始化队列;否则,将新节点添加到队列的尾部,并更新tail指针。 3. CAS操作:AQS的CAS操作是通过compareAndSetHead和compareAndSetTail方法实现的。这些方法使用CAS操作来更新head和tail指针,保证操作的原子性。 4. 出队操作:AQS的出队操作是通过deq方法实现的。在出队操作中,首先判断队列是否为空,如果为空,则返回null;否则,将头节点出队,并更新head指针。 5. 同步状态的获取和释放:AQS提供了acquire和release方法来获取和释放同步状态。acquire方法用于获取同步状态,如果获取失败,则会将当前线程加入到等待队列中;release方法用于释放同步状态,并唤醒等待队列中的线程。 通过继承AQS类,可以实现自定义的同步器。具体的实现方式是重写AQS的几个关键方法,如tryAcquire、tryRelease等,来实现对同步状态的获取和释放。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值