JAVA面试考点—— CountDownLatch和Semaphore的区别和底层原理

本文概述了CountDownLatch和Semaphore在并发编程中的关键用法,包括CountDownLatch作为线程等待计数器的示例,以及它们如何通过AQS实现并发控制。还介绍了降级和熔断策略的应用,以及源码层面的分析,展示了它们的工作原理和唤醒机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 目录

CountDownLatch、CyclicBarrier、Semaphore 用法总结https://segmentfault.com/a/1190000012234469

1. 初识CountDownlatch

1.1 使用示例

1.2 降级、熔断

2. 源码分析

2.1 主任务流程

2.2 子任务执行流程

3、总结


CountDownLatch和Semaphore使用场景https://segmentfault.com/a/1190000038672250

CountDownLatch、CyclicBarrier、Semaphore 用法总结https://segmentfault.com/a/1190000012234469

1. 初识CountDownlatch

CountDownLatch这个同步工具允许一条或多条线程,等待其他线程中的一组操作完成后再继续执行。

1.1 使用示例

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.ReentrantLock;

@SpringBootApplication
public class DemoApplication {

    private final static Random random = new Random();

    static class SearchTaks implements Runnable {
        private Integer id;
        private CountDownLatch latch;

        public SearchTaks(Integer id, CountDownLatch latch) {
            this.id = id;
            this.latch = latch;
        }

        @Override
        public void run() {
            System.out.println("线程" + id + "开始运行");
            int seconds = random.nextInt(10);
            try {
                Thread.sleep(seconds * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("耗时" + seconds + "s, 线程id = " + id);
            latch.countDown();
        }
    }


    public static void main(String[] args) {
        List<Integer> idList = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
        CountDownLatch latch = new CountDownLatch(idList.size());
        for (Integer id : idList) {
            Thread thread = new Thread(new SearchTaks(id, latch));
            thread.start();
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("七个任务完成");
    }
}

 

1.2 降级、熔断

在真实的业务中尤其是对外部系统调用,耗时往往是不可控的。一旦该任务卡死,如果没有对应的策略,那么很可能造成下游的级联失败。导致整个调用失效,严重的话可能会导致任务堆积,甚至整个系统的崩溃。

所以要设计一些降级和熔断策略,设置超时时间只是其中一种量简单的处理方式。

2. 源码分析


package java.util.concurrent;

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class CountDownLatch {
    /**
     * Synchronization control For CountDownLatch.
     * Uses AQS state to represent count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

        // 重写了父类(AQS)的tryAcquireShared方法
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c - 1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

    private final Sync sync;

    /**
     * Constructs a {@code CountDownLatch} initialized with the given count.
     *
     * @param count the number of times {@link #countDown} must be invoked
     *        before threads can pass through {@link #await}
     * @throws IllegalArgumentException if {@code count} is negative
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    /**
     * Causes the current thread to wait until the latch has counted down to
     * zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
     *
     * <p>If the current count is zero then this method returns immediately.
     *
     * <p>If the current count is greater than zero then the current
     * thread becomes disabled for thread scheduling purposes and lies
     * dormant until one of two things happen:
     * <ul>
     * <li>The count reaches zero due to invocations of the
     * {@link #countDown} method; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread.
     * </ul>
     *
     * <p>If the current thread:
     * <ul>
     * <li>has its interrupted status set on entry to this method; or
     * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
     * </ul>
     * then {@link InterruptedException} is thrown and the current thread's
     * interrupted status is cleared.
     *
     * @throws InterruptedException if the current thread is interrupted
     *         while waiting
     */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    /**
     * Causes the current thread to wait until the latch has counted down to
     * zero, unless the thread is {@linkplain Thread#interrupt interrupted},
     * or the specified waiting time elapses.
     *
     * <p>If the current count is zero then this method returns immediately
     * with the value {@code true}.
     *
     * <p>If the current count is greater than zero then the current
     * thread becomes disabled for thread scheduling purposes and lies
     * dormant until one of three things happen:
     * <ul>
     * <li>The count reaches zero due to invocations of the
     * {@link #countDown} method; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread; or
     * <li>The specified waiting time elapses.
     * </ul>
     *
     * <p>If the count reaches zero then the method returns with the
     * value {@code true}.
     *
     * <p>If the current thread:
     * <ul>
     * <li>has its interrupted status set on entry to this method; or
     * <li>is {@linkplain Thread#interrupt interrupted} while waiting,
     * </ul>
     * then {@link InterruptedException} is thrown and the current thread's
     * interrupted status is cleared.
     *
     * <p>If the specified waiting time elapses then the value {@code false}
     * is returned.  If the time is less than or equal to zero, the method
     * will not wait at all.
     *
     * @param timeout the maximum time to wait
     * @param unit the time unit of the {@code timeout} argument
     * @return {@code true} if the count reached zero and {@code false}
     *         if the waiting time elapsed before the count reached zero
     * @throws InterruptedException if the current thread is interrupted
     *         while waiting
     */
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    /**
     * Decrements the count of the latch, releasing all waiting threads if
     * the count reaches zero.
     *
     * <p>If the current count is greater than zero then it is decremented.
     * If the new count is zero then all waiting threads are re-enabled for
     * thread scheduling purposes.
     *
     * <p>If the current count equals zero then nothing happens.
     */
    public void countDown() {
        sync.releaseShared(1);
    }

    /**
     * Returns the current count.
     *
     * <p>This method is typically used for debugging and testing purposes.
     *
     * @return the current count
     */
    public long getCount() {
        return sync.getCount();
    }

    /**
     * Returns a string identifying this latch, as well as its state.
     * The state, in brackets, includes the String {@code "Count ="}
     * followed by the current count.
     *
     * @return a string identifying this latch, as well as its state
     */
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

通过分析上述源码,可以得到如下调用链(图中黄色为CountDownLatch中的api、绿色为AQS中的api、蓝色为内部类Sync)

2.1 主任务流程

1. 主任务调用latch中的await方法,等待子任务全部执行完毕

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

2. await调用AQS中的 acquireSharedInterruptibly方法,先以 tryAcquireShared 方法判断以共享模式获取锁释放能成功

对于 tryAcquireShared 方法,可以看下AQS源码中的返回值注释:               

  • 如果返回值的负数,则代表获取锁失败
  • 返回0,则代表获取锁成功,不唤醒后续节点;
  • 如果返回正数,则获取锁成功,唤醒后续节点
    /**
     * @return a negative value on failure; zero if acquisition in shared
     *         mode succeeded but no subsequent shared-mode acquire can
     *         succeed; and a positive value if acquisition in shared
     *         mode succeeded and subsequent shared-mode acquires might
     *         also succeed, in which case a subsequent waiting thread
     *         must check availability. (Support for three different
     *         return values enables this method to be used in contexts
     *         where acquires only sometimes act exclusively.)  Upon
     *         success, this object has been acquired.
     */
    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }

我们再看下countDownLatch方法中,内部类Sync是如何重写方法 tryAcquireShared 的

    /**
     * Synchronization control For CountDownLatch.
     * Uses AQS state to represent count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        ....

        // 查询了一下state便直接返回,并没有获取锁,因为主线程不需要获取
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        ....
    }

Sync内部类中,tryAcquireShared 方法只是查询了一下state便直接返回,并没有获取锁,因为主线程不需要获取锁,原因如下:

 3. acquireSharedInterruptibly 如何等待子任务完成呢?

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

看下 doAcquireSharedInterruptibly 中的实现过程:

   /**
     * Acquires in shared interruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        try {
            // 自旋
            for (;;) {
                // 判断当前节点的前驱节点是不是头节点
                // AQS中,头节点是虚节点,一个node在完成使命后会变成head节点
                final Node p = node.predecessor();
                if (p == head) { // 说明当前节点可以进行获取锁操作
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r); // 将当前节点设置成head
                        p.next = null; // help GC
                        return;
                    }
                }
                // 被唤醒的节点将在for循环中继续执行,唤醒后续线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }

2.2 子任务执行流程

1. 子任务调用latch中的 countDown 方法释放锁

    /**
     * Decrements the count of the latch, releasing all waiting threads if
     * the count reaches zero.
     *
     * <p>If the current count is greater than zero then it is decremented.
     * If the new count is zero then all waiting threads are re-enabled for
     * thread scheduling purposes.
     *
     * <p>If the current count equals zero then nothing happens.
     */
    public void countDown() {
        sync.releaseShared(1);
    }

2.  releaseShared 方法中进行唤醒


    /**
     * Releases in shared mode.  Implemented by unblocking one or more
     * threads if {@link #tryReleaseShared} returns true.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryReleaseShared} but is otherwise uninterpreted
     *        and can represent anything you like.
     * @return the value returned from {@link #tryReleaseShared}
     */
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

3、总结

CountDownLatch

表示计数器,可以给CountDownLatch设置一个数字

一个线程调用CountDownLatch的await()方法,将会阻塞

其他线程可以调用CountDownLatch的countDown()方法来对CountDownLatch中的数字减一, 当数字被减成0后,所有await的线程都将被唤醒。

对应的底层原理就是,调用await()方法的线程会利用AQS排队,一旦数字被减为0,则会将AQS中排队的线程依次唤醒。

Semaphore

表示信号量,可以设置许可的个数,表示同时允许最多多少个线程使用该信号量

通过acquire()来获取许可, 如果没有许可可用则线程阻塞,并通过AQS来排队,可以通过release()方法来释放许可

当某个线程释放了某个许可后,会从AQS中正在排队的第一个线程开始依次唤醒,直到没有空闲许可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值