Semaphore、CountdownLatch、CyclicBarrier详解

1.Semaphore

信号量,用来限制能同时访问共享资源的线程上限。

1.1 基本使用

/**
 * @ClassName SemaphoreTest
 * @author: shouanzh
 * @Description 信号量,用来限制能同时访问共享资源的线程上限。
 * @date 2022/3/20 11:32
 */
@Slf4j
public class SemaphoreTest {

    public static void main(String[] args) {

        // 1. 创建 Semaphore 对象
        Semaphore semaphore = new Semaphore(3); // 限制上限为3

        // 2. 10个线程同时运行
        for (int i = 0; i < 10; i++) {

            new Thread(()->{
                try {
                    // 3.获得此信号量
                    semaphore.acquire();
                    log.debug("获得信号量");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 4.释放信号量
                    semaphore.release();
                }
            },"t" + i).start();

        }

    }

}

运行结果

// 每次运行三个线程
2022-03-20 11:45:05 [t0] - 获得信号量
2022-03-20 11:45:05 [t2] - 获得信号量
2022-03-20 11:45:05 [t1] - 获得信号量

2022-03-20 11:45:07 [t3] - 获得信号量
2022-03-20 11:45:07 [t4] - 获得信号量
2022-03-20 11:45:07 [t5] - 获得信号量

2022-03-20 11:45:09 [t6] - 获得信号量
2022-03-20 11:45:09 [t7] - 获得信号量
2022-03-20 11:45:09 [t8] - 获得信号量

2022-03-20 11:45:11 [t9] - 获得信号量

Process finished with exit code 0

1.2 Semaphore原理-图解流程

加锁解锁流程

Semaphore 有点像一个停车场,permits 就好像停车位数量,当线程获得了 permits 就像 是获得了停车位,然后停车场显示空余车位减一

刚开始,permits (state)为3,这时5个线程来获取资源
在这里插入图片描述
假设其中 Thread-1, Thread-2, Thread-4 CAS竞争成功,而 Thread-0 和 Thread-3竞争失 败,进入AQS 队列park阻塞

在这里插入图片描述

这时 Thread-4释放了 permits,状态如下
在这里插入图片描述

接下来 Thread-0竞争成功,permits 再次设置为0,设置自己为 head节点,断开原来的 head 节点, unpark接下来的 Thread-3 节点,但由于 permits 是0,因此 Thread-3 在尝试 不成功后再次进入 park状态

在这里插入图片描述

1.3 Semaphore原理-源码分析

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;
    NonfairSync(int permits) {
        // permits 即 state
        super(permits);
    }

    // Semaphore 方法, 方便阅读, 放在此处
    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    
    // AQS 继承过来的方法, 方便阅读, 放在此处
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

    // 尝试获得共享锁
    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }

    // Sync 继承过来的方法, 方便阅读, 放在此处
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            int available = getState();
            int remaining = available - acquires;
            if (
                // 如果许可已经用完, 返回负数, 表示获取失败, 进入 doAcquireSharedInterruptibly
                    remaining < 0 ||
                            // 如果 cas 重试成功, 返回正数, 表示获取成功
                            compareAndSetState(available, remaining)
            ) {
                return remaining;
            }
        }
    }

    // AQS 继承过来的方法, 方便阅读, 放在此处
    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) {
                    // 再次尝试获取许可
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        // 成功后本线程出队(AQS), 所在 Node设置为 head
                        // 如果 head.waitStatus == Node.SIGNAL ==> 0 成功, 下一个节点 unpark
                        // 如果 head.waitStatus == 0 ==> Node.PROPAGATE
					  // r 表示可用资源数, 为 0 则不会继续传播
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                // 不成功, 设置上一个节点 waitStatus = Node.SIGNAL, 下轮进入 park 阻塞
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    // Semaphore 方法, 方便阅读, 放在此处
    public void release() {
        sync.releaseShared(1);
    }

    // AQS 继承过来的方法, 方便阅读, 放在此处
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

    // 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))
                return true;
        }
    }
    
    private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        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;
        }
    }
}

2.CountdownLatch

用来进行线程同步协作,它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行。

其中构造参数用来初始化等待计数值,await()用来等待计数归零,countDown()用来让计数减一

CountDownLatch 是共享锁的一种实现,它默认构造 AQS 的 state 值为 count。当线程使用 countDown方法时,其实使用了 tryReleaseShared 方法以CAS 的操作来减少 state ,直至 state 为 0 就代表所有的线程都调用了countDown方法。当调用 await 方法的时候,如果 state 不为0,就代表仍然有线程没有调用 countDown 方法,那么就把已经调用过 await 的线程都放入阻塞队列 Park ,并自旋 CAS 判断 state == 0,直至最后一个线程调用了 countDown ,使得 state == 0,于是阻塞的线程便判断成功,全部往下执行。

代码举例

/**
 * CountDownLatchTest 用来进行线程同步协作,它允许一个或多个线程一直等待,
 * 直到其他线程的操作执行完毕再执行。
 */
@Slf4j
public class CountDownLatchTest {

    public static void main(String[] args) throws InterruptedException {
        method3();
    }

    /**
     * 基本使用
     * @throws InterruptedException
     */
    public static void method1() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(3);

        new Thread(() -> {
            log.debug("begin...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
            log.debug("end...");
        },"t1").start();

        new Thread(() -> {
            log.debug("begin...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
            log.debug("end...");
        },"t2").start();

        new Thread(() -> {
            log.debug("begin...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
            log.debug("end...");
        },"t3").start();

        log.debug("await...");
        countDownLatch.await();
        log.debug("await end...");

        /**
         * 2022-03-20 13:07:14 [t3] - begin...
         * 2022-03-20 13:07:14 [main] - await...
         * 2022-03-20 13:07:14 [t1] - begin...
         * 2022-03-20 13:07:14 [t2] - begin...
         * 2022-03-20 13:07:15 [t1] - end...
         * 2022-03-20 13:07:16 [t2] - end...
         * 2022-03-20 13:07:17 [t3] - end...
         * 2022-03-20 13:07:17 [main] - await end...
         *
         * Process finished with exit code 0
         */
    }

    /**
     * 改进
     */
    public static void method2() {
        CountDownLatch countDownLatch = new CountDownLatch(3);
        ExecutorService executorService = Executors.newFixedThreadPool(4);

        executorService.submit(() -> {
            log.info("t1 start ...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
            log.info("t1 end ...{}", countDownLatch.getCount());
        });

        executorService.submit(() -> {
            log.info("t2 start ...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
            log.info("t2 end ...{}", countDownLatch.getCount());
        });

        executorService.submit(() -> {
            log.info("t3 start ...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
            log.info("t3 end ...{}", countDownLatch.getCount());
        });

        // 等待其它线程执行完成
        executorService.submit(() -> {
            log.info("main wait ...");
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("main wait end ...");
            executorService.shutdown();
        });

        /**
         * 2022-03-20 13:50:20 [pool-1-thread-2] - t2 start ...
         * 2022-03-20 13:50:20 [pool-1-thread-3] - t3 start ...
         * 2022-03-20 13:50:20 [pool-1-thread-1] - t1 start ...
         * 2022-03-20 13:50:20 [pool-1-thread-4] - main wait ...
         * 2022-03-20 13:50:21 [pool-1-thread-1] - t1 end ...2
         * 2022-03-20 13:50:22 [pool-1-thread-2] - t2 end ...1
         * 2022-03-20 13:50:23 [pool-1-thread-3] - t3 end ...0
         * 2022-03-20 13:50:23 [pool-1-thread-4] - main wait end ...
         *
         * Process finished with exit code 0
         */
    }

    /**
     * 用来进行线程同步协作,它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行。
     * @throws InterruptedException
     */
    public static void method3() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        String[] all = new String[10];
        Random random = new Random();
        for(int i = 0; i < 10; i++) {
            int id = i;
            executorService.submit(() -> {
                for (int j = 0; j <= 100; j++) {
                    try {
                        Thread.sleep(random.nextInt(100));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    all[id] = j + "%";
                    System.out.print("\r" + Arrays.toString(all));
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        System.out.println();
        log.debug("游戏开始...");
        executorService.shutdown();

        /**
         * [100%, 100%, 100%, 100%, 100%, 100%, 100%, 100%, 100%, 100%]
         * 2022-03-20 14:01:34 [main] - 游戏开始...
         *
         * Process finished with exit code 0
         */
    }

}

3.CyclicBarrier

循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置『计数个数』,每个线程执行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足『计数个数』时,继续执行。跟 CountdownLatch 一样,但这个可以重用。

/**
 * @ClassName CyclicBarrierTest
 * @author: shouanzh
 * @Description 循环栅栏,用来进行线程协作,等待线程满足某个计数。
 * 构造时设置『计数个数』,每个线程执行到某个需要“同步”的时刻调用 await() 方法进行等待,
 * 当等待的线程数满足『计数个数』时,继续执行。跟 CountdownLatch 一样,但这个可以重用。
 * @date 2022/3/20 14:38
 */
@Slf4j
public class CyclicBarrierTest {

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(2);

        CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
            log.info("task1,task2 finish ...");
        });

        for(int i = 0; i < 3; i++) {
            executorService.submit(() -> {
                log.info("task1 begin ...");
                try {
                    Thread.sleep(1000);
                    cyclicBarrier.await(); // 2-1 = 1
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });

            executorService.submit(() -> {
                log.info("task2 begin ...");
                try {
                    Thread.sleep(2000);
                    cyclicBarrier.await(); // 1-1 = 0
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
        }

        executorService.shutdown();
    }

}

运行结果

2022-03-20 14:52:06 [pool-1-thread-2] - task2 begin ...
2022-03-20 14:52:06 [pool-1-thread-1] - task1 begin ...
2022-03-20 14:52:08 [pool-1-thread-2] - task1,task2 finish ...
2022-03-20 14:52:08 [pool-1-thread-2] - task1 begin ...
2022-03-20 14:52:08 [pool-1-thread-1] - task2 begin ...
2022-03-20 14:52:10 [pool-1-thread-1] - task1,task2 finish ...
2022-03-20 14:52:10 [pool-1-thread-1] - task1 begin ...
2022-03-20 14:52:10 [pool-1-thread-2] - task2 begin ...
2022-03-20 14:52:12 [pool-1-thread-2] - task1,task2 finish ...

Process finished with exit code 0
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值