多线程并发工具原理解析(CountDownLatch,CyclicBarrier,Semaphore)

CountDownLatch

源码解析

CountDownLatch主要是两个方法:await()、countDown(),还有一个构造方法 CountDownLatch(int count)

构造方法:CountDownLatch(int count)

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        // 实例化一个Sync对象
        this.sync = new Sync(count);
    }


通过构造方法去设置AQS state的初始值为count,Sync是在CountDownLatch类中实现的AQS实现类

阻塞方法:await()

await()会阻塞主线程,直到所有线程都countDown()数量等于count,也就是state==0

    public void await() throws InterruptedException {
        // 共享式获取AQS的同步状态
        sync.acquireSharedInterruptibly(1);
    }

调用的是AQS的acquireSharedInterruptibly方法:

	public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted()) // 线程中断 说明闭锁对线程中断敏感
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0) // 闭锁未使用完成 线程进入同步队列自旋等待 
            doAcquireSharedInterruptibly(arg);
    }

tryAcquireShared(int acquires):

    /** 获取共享锁 */
    protected int tryAcquireShared(int acquires) {
        // AQS的同步状态为0则闭锁结束 可以进行下一步操作
        return (getState() == 0) ? 1 : -1;
    }

也就是需要当getState() == 0的时候,才可以进行继续执行,否则线程进入同步队列自旋等待(AQS同步队列的自旋等待)

计数方法:countDown()

调用countDown()方法会将计数器减1,直到计数器为0,代码如下:

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

同样调用的是AQS的releaseShared方法:

	public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) { // 减少闭锁的计数器,只有计数器为0的时候才会返回true
            doReleaseShared(); // 唤醒被await方法阻塞的所有线程
           return true;
        }
        return false;
    }

这里的返回这对CountDownLatch没有用,其中tryReleaseShared方法依赖的是Sync的实现:

        /** 释放共享锁 */
        protected boolean tryReleaseShared(int releases) {
            // 死循环,如果CAS操作失败就会不断继续尝试
            for (;;) {
                int c = getState();
                if (c == 0) // 正常不会进入此逻辑
                    return false;
                int nextc = c-1; // 将计数器-1
                if (compareAndSetState(c, nextc)) // 更新计数器
                    // 如果操作成功,返回计数器是否为0,直接关系到是否执行doReleaseShared方法来唤醒后续线程
                    return nextc == 0;
            }
        }

可以看到,只有当计数器等于0的时候才会返回true,才会唤醒后续线程(调用await()自旋等待的线程)

总结

  1. 在await()的时候,调用await()的线程将进入自旋等待,自旋过程会调用AQS的Sync实现的tryAcquireShared方法,只有当闭锁计数器等于0的时候,线程才能够继续执行
  2. 在其它线程调用countDown的方法时候,会将闭锁计数器减1(state-1),直至计数器等于0的时候,会唤醒后续线程获取闭锁(doReleaseShared),自旋等待中的线程才可以继续执行

Semaphore源码解析

关键方法如下:

  1. 构造方法:new Semaphore(5);
  2. 获取许可:semaphore.acquire();
  3. 释放许可:semaphore.release();

接下来我们从这三个方法入手进行源码解析

构造方法:new Semaphore(5)

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
 
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

默认构造方法为非公平共享锁,可以通过构造参数fair来选择公平或非公平,类似于ReentantLock

获取许可:semaphore.acquire()

    public void acquire() throws InterruptedException {
        // 共享式获取AQS的同步状态
        sync.acquireSharedInterruptibly(1);
    }

调用的是AQS的acquireSharedInterruptibly方法:

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

其中tryAcquireShared依赖于Sync实现,在Semaphore中有AQS的实现Sync类,方法如下:

        // 尝试获取共享锁
        protected int tryAcquireShared(int acquires) {
            for (; ; ) {
                // 队列中存在等待线程则返回-1
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState(); // 可用许可数量
                int remaining = available - acquires; // 剩余许可数量

                if (remaining < 0 || compareAndSetState(available, remaining))
                    // 返回可用的余量
                    return remaining;
            }
        }

这是FairSync的tryAcquireShared方法,在NonfairSync中,没有hasQueuedPredecessors()判断,其余一样。

在方法中可以看出,最终返回的是剩余的许可数量,有如下几种情况:

    private void doAcquireSharedInterruptibly(int arg)
            throws InterruptedException {
        // 线程进入同步队列
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (; ; ) { // 自旋
                final Node p = node.predecessor();
                if (p == head) { // 当前节点的前置节点是AQS的头节点 即自己是AQS同步队列的第一个节点
                    int r = tryAcquireShared(arg); // 再去获取信号量
                    if (r >= 0) {
                        setHeadAndPropagate(node, r); // 退出自旋
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                // 判断是否应该挂起该线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt()) {
                    throw new InterruptedException();
                }
            }
        } finally {
            if (failed) {
                cancelAcquire(node); // 获取失败 就取消获取
            }
        }
    }

  1. 如果剩余许可数量<0,则执行doAcquireSharedInterruptibly方法让线程自旋等待,这里是等待别的线程释放许可后线程被唤醒去尝试获取
  2. 否则就是拿到了许可数量,继续正常执行,不阻塞

释放许可:semaphore.release()

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

同样,调用的是AQS的releaseShared方法,看下代码:

    public final boolean releaseShared(int arg) {
        // 调用AQS实现类的tryReleaseShared
        if (tryReleaseShared(arg)) {
            // 唤醒后续的线程节点
            doReleaseShared();
            return true;
        }
        return false;
    }

tryReleaseShared交由子类Sync实现,代码如下:

        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)) // CAS更新当前信号量许可数
                    return true;
            }
        }

释放许可成功则继续调用AQS的doReleaseShared方法来唤醒后续节点可以来争取许可了

    private void doReleaseShared() {
        for (; ; ) { // 自旋等待
            Node h = head;
            // 有头节点且头节点和尾节点不是同一个
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    // 设置status为0
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) {
                        continue; // 循环检查
                    }
                    // 唤醒节点的后续节点
                    unparkSuccessor(h);
                } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) { //
                    continue; // 失败则继续循环
                }
            }
            if (h == head) {
                break;
            }
        }
    }

总结

Semaphore使用AQS同步状态来保存信号量的计数器。
acquireSharedInterruptibly会减少计数(获取许可),当计数为非正值的时候阻塞线程,否则不会阻塞线程
releaseShared方法会增加计数(释放许可),在计数不超过信号量限制时会解除线程的阻塞(获取到许可的线程)

阻塞方法:cyclicBarrier.await()

核心dowait(boolean timed, long nanos)

    private int dowait(boolean timed, long nanos)
            throws InterruptedException, BrokenBarrierException,
            TimeoutException {
        // 使用独占锁来执行dowait方法,并发性可能不是很高
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 当前代
            final Generation g = generation;
            // 如果当前代损坏了则抛出异常
            if (g.broken)
                throw new BrokenBarrierException();

            // 如果线程中断则抛出异常
            if (Thread.interrupted()) {
                // 将损坏状态设置为true,并通知其他阻塞在此栅栏上的线程
                breakBarrier();
                throw new InterruptedException();
            }

            // 获取下标
            int index = --count;
            // 如果是 0,说明最后一个线程调用了该方法
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    // 执行栅栏任务
                    if (command != null)
                        command.run();
                    ranAction = true;
                    // 更新一代,将count重置,将generation重置
                    nextGeneration();
                    return 0;
                } finally {
                    // 如果执行栅栏任务的时候失败了,就将损坏状态设置为true
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // 自旋直到触发、broken、中断或超时
            for (; ; ) {
                try {
                    // 如果没有时间限制,则直接等待,直到被唤醒
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L) // 如果有时间限制,则等待指定时间
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    // 当前代没有损坏
                    if (g == generation && !g.broken) {
                        breakBarrier(); // 让栅栏失效
                        throw ie;
                    } else {
                        // 上面条件不满足,说明这个线程不是这代的, 就不会影响当前这代栅栏的执行,所以,就打个中断标记
                        Thread.currentThread().interrupt();
                    }
                }

                // 当有任何一个线程中断了,就会调用breakBarrier方法,就会唤醒其他的线程,其他线程醒来后,也要抛出异常
                if (g.broken)
                    throw new BrokenBarrierException();

                // g != generation表示正常换代了,返回当前线程所在栅栏的下标
                // 如果 g == generation,说明还没有换代,那为什么会醒了?
                // 因为一个线程可以使用多个栅栏,当别的栅栏唤醒了这个线程,就会走到这里,所以需要判断是否是当前代。
                // 正是因为这个原因,才需要generation来保证正确。
                if (g != generation)
                    return index;

                // 如果有时间限制,且时间小于等于0,销毁栅栏并抛出超时异常
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }


  1. 执行一些验证:栅栏是否broken、线程是否中断

  2. 如果是最后一个线程调用dowait,则执行栅栏任务barrierAction,然后更新代nextGeneration

  3. 如果不是最后一个线程调用dowait,则自旋,trip.await()会进行阻塞,直至发生如下情况才会被唤醒或终止:
    1.最后一个线程到达,即index==0
    2.某个参与栅栏的线程等待超时
    3.某个参与栅栏的线程被中断
    4.调用了CyclicBarrier的reset()方法,该方法会将屏障置为初始状态

  4. 在被唤醒之后,栅栏没有损坏且是同一代,则返回下标index

https://blog.csdn.net/qq_32828253/article/details/111936100?spm=1001.2014.3001.5502
https://blog.csdn.net/qq_32828253/article/details/111937784?spm=1001.2014.3001.5502
https://blog.csdn.net/qq_32828253/article/details/111934945?spm=1001.2014.3001.5502

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值