CountDownLatch 和 CyclicBarrier 用法以及区别

在使用多线程执行任务时,通常需要在主线程进行阻塞等待,直到所有线程执行完毕,主线程才能继续向下执行,主要有以下几种可选方式

1. 调用 main 线程的 sleep 方法

1)睡眠预估时间
一般用于预估线程的执行时间,在主线程内执行线程sleep方法阻塞线程,如下方式:

public class Main {
    public synchronized static void print(){System.out.println("abc");}
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                print();
            }).start();
        }
        Thread.sleep(1000);
    }
}

这种方式的缺点就是,线程执行的时间与数量和其任务执行的长短有关,一般很难去预估。

2)调用 join 阻塞主线程

public class Main {
    public synchronized static void print() {
        try {
            System.out.println("abc");
            Thread.sleep(2000);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> print());
        t1.start();
        // 阻塞主线程, 直到 t1 线程执行完毕
        t1.join();
        System.out.println("t1 执行完成");
    }
}

缺点:Thread.join()方法只能支持阻塞等待一个子线程执行完毕

2. 使用CountDownLatch

CountDownLatch 提供了一个阻塞阀门,当阀门 count 变成 0 时候放行

  • 首先CountDownLatch会初始化线程数量为实际线程的运行数量
  • 每当一个线程执行完毕后,会把count - 1
  • 主线程调用countDownLatch.await()方法进行阻塞,当count == 0时,则所有线程执行完毕,主线程开始继续向下执行
// 100 个线程打印abc, 等到所有线程执行结束, 主线程开始继续向下执行
public class Main {
    public synchronized static void print(){System.out.println("abc");}
    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        // CountDownLatch缺点: CountDownLatch是一次性的, 使用完毕后不能再对其设置值
        CountDownLatch countDownLatch = new CountDownLatch(100);
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                // 执行线程任务
                print();
                // 执行完毕 --- 将 countDownLatch - 1
                countDownLatch.countDown();
            }).start();
        }
        // 主线程因为之前的线程没有执行完阻塞在这里
        // 当所有线程执行完毕后, 主线程会继续执行
        countDownLatch.await();
        System.out.println("线程执行结束:");
        System.out.println("执行时间为: " + (System.currentTimeMillis() - start) + "ms");
    }
}

3. 使用 CyclicBarrier

CyclicBarrier 也是一种多线程执行时候的控制器,而对于CyclicBarrier来说,重点是那一组N个线程,他们之间任何一个没有完成,所有的线程都必须等待,当计数器到达指定值时,用法如下:

public class Main {
    public synchronized static void print(){System.out.println("abc");}
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        // CyclicBarrier 线程执行控制器 --- 可重用
        // 当所有线程到达栅栏, 然后触发回调函数
        CyclicBarrier barrier = new CyclicBarrier(100, ()->{
            long end = System.currentTimeMillis();
            System.out.println("线程执行结束:");
            System.out.println("线程执行所需时间:" + (end - start));
        });
        for(int i=0; i<100; i++){
            new Thread(()->{
                print();
                try {
                    barrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

4. CountDownLatch 和 CyclicBarrier 区别

CountDownLanchCyclicBarrier
减计数方式加计数方式
count为0时释放所有等待的线程计数为指定值时释放所有等待的线程
count为0时可以重置计数置为指定值时,计数为0重新开始
子线程调用countDown()方法将计数器-1,主线程调用await()方法进行阻塞子线程调用await方法将计数器+1,当加后的值不等于指定值,当前线程阻塞
不可重复利用可重复利用

5. Semaphore信号量机制

Semaphore可以阻塞线程并且可以控制同时访问线程的个数,通过acquire()获取一个许可,如果没有获取到就继续等待,通过release()释放一个许可。Semaphore和锁有点类似,都可以控制对某个资源的访问权限,通常用作对共享区资源访问的限流作用。

  • 初始化许可数量:Semaphore semaphore=new Semaphore(10)
  • 获取许可:semaphore.acquire() 默认值是1
  • 释放许可:semaphore.release() 默认值是1
使用场景,Semaphore适合控制并发数:

Semaphore可以用来做流量分流,特别是对公共资源有限的场景,比如数据库连接。
假设有这个的需求,读取几万个文件的数据到数据库中,由于文件读取是IO密集型任务,可以启动几十个线程并发读取,但是数据库连接数只有10个,这时就必须控制最多只有10个线程能够拿到数据库连接进行操作。这个时候,就可以使用Semaphore做流量控制

public class SemaphoreTest {
    public static void main(String[] args) {
        ExecutorService  executor=Executors.newFixedThreadPool(40);
        // 有10个许可,同一时间只能有10个线程工作
        Semaphore  semaphore = new Semaphore(10);
        for (int i = 0; i < 40; i++) {
            executor.execute(()->{
                try {
                    // 获取许可,未获取到许可的线程去阻塞
                    semaphore.acquire();
                    System.out.println("处理数据中......");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally{
                    // 释放许可,给阻塞线程使用
                	semaphore.release();
                }
            });
        }
        executor.shutdown();
    }
}
注意:

当初始化许可数量为1时,此时semaphore就变成了一个互斥锁mutex,支持对临界区资源的互斥访问。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值