JUC学习:CountDownLatch,CyclicBarrier,Semaphore

CountDownLatch&CyclicBarrier

从字面上理解,CountDown表示减法计数,Latch表示门闩的意思,计数为0的时候就可以打开门闩了。 CyclicBarrier表示循环的障碍物。两个类都含有这一个意思:对应的线程都完成工作之后再进行下一步动作,也就是大家都准备好之后再进行下一步。然而两者最大的区别是,进行下一步动作的动作实施者是不一样的。

这里的“动作实施者”有两种,一种是主线程(即执行main函数),另一种是执行任务的其他线程,后面叫这种线程为“其他线程”,区分于主线程。对于CountDownLatch,当计数为0的时候,下一步的动作实施者是main函数;对于CyclicBarrier,下一步动作实施者是“其他线程”。

CyclicBarrier的特性就在于它的计数器是可以重置的,这也就有了上图那种多屏障的情况,虽然第一次调用await使得计数器等于0屏障1失效,但是后续如果继续调用await,屏障还能继续使用,这也就是计数器重置的好处。而CountDownLatch第一次屏障失效后,就结束了

CountDownLatch用法实例

主要方法是

latch.countDown();减法器

latch.await();唤醒

//CyclicBarrier使用测试,成功案例
@Test
void threadTest7() throws Exception {
    //这个是接收Future的线程安全的集合
    List<Future<List<User>>> list = Collections.synchronizedList(new ArrayList<>());
    //CyclicBarrier 拦截器,只有8个线程任务都到了 cyclicBarrier.await()方法(即减到0)才能放行,不然全部阻塞(里面开可以再开一个子线程,案例看下面)
    CountDownLatch latch = new CountDownLatch(8);
    for (int i = 0; i < 8; i++) {
       //这里是采用线程池的方式启动线程,submit就是启动callable接口,会有返回值future
        Future<List<User>> future = executor.submit((() -> {
            //因为会涉及到线程安全问题,所以就要用线程安全的集合synchronizedList
            List<User> userList = Collections.synchronizedList(new ArrayList<>());
             //模拟获取大量数据
            for (int j = 0; j < 2000; j++) {
                //这里是一个获得数据的方法,读者可以自己定义
                User user = asyncTaskService.getUser();
                userList.add(user);
            }
            log.info("当前线程=>" + Thread.currentThread().getName() + "===正在获取数据");
//这里设置阻塞,只有8个线程都完成了上面的循环,来到这里,进行减一,只有减到0,才能往后继续,否则先到的线程就会继续阻塞
            latch.countDown();
            return userList;
        }));
        list.add(future);
    }
    //当上面线程完成任务了,会回收回线程池中,这里就是主线程往下运行了
    latch.await();//唤醒主线程
    int size1 = list.size();
    for (int i = 0; i < size1; i++) {
        //得到返回结果的List<User>
        Future<List<User>> future = list.get(i);
        List<User> userList = null; 
        //判断任务是否返回成功,这里其实可以去掉这个判断,因为这里不是主线程和子线程并行,latch.await()让子线程都运行完回到线程池了,才唤醒的主线程
            if (future.isDone()) {
                try {
                    userList = future.get();
                    log.info("这是第" + i + "返回结果,总共有" + userList.size() + "个结果");
                    long currentTimeMillis = System.currentTimeMillis();
                    //userService.saveBatch(userList);可以将数据录入数据库中
                    log.info("使用的时间是==>" + (System.currentTimeMillis() - currentTimeMillis));
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
        }
    }

}


CyclicBarrier

主要方法: cyclicBarrier.await();计数器

CyclicBarrier用法案例

//CyclicBarrier使用测试,成功案例
@Test
void threadTest7() throws Exception {
    //这个是接收Future的线程安全的集合
    List<Future<List<User>>> list = Collections.synchronizedList(new ArrayList<>());
    //CyclicBarrier 拦截器,只有8个线程任务都到了 cyclicBarrier.await()方法(即减到0)才能放行,不然全部阻塞(里面开可以再开一个子线程,案例看下面)
    CyclicBarrier cyclicBarrier = new CyclicBarrier(8);
    for (int i = 0; i < 8; i++) {
       //这里是采用线程池的方式启动线程,submit就是启动callable接口,会有返回值future
        Future<List<User>> future = executor.submit((() -> {
            //因为会涉及到线程安全问题,所以就要用线程安全的集合synchronizedList
            List<User> userList = Collections.synchronizedList(new ArrayList<>());
             //模拟获取大量数据
            for (int j = 0; j < 2000; j++) {
                //这里是一个获得数据的方法,读者可以自己定义
                User user = asyncTaskService.getUser();
                userList.add(user);
            }
            log.info("当前线程=>" + Thread.currentThread().getName() + "===正在获取数据");
//这里设置阻塞,只有8个线程都完成了上面的循环,来到这里,进行减一,只有减到0,才能往后继续,否则先到的线程就会继续阻塞,但并不会影响主线程的继续运行
            cyclicBarrier.await();
            return userList;
        }));
        list.add(future);
    }
    //当上面线程完成任务了,会回收回线程池中,这里就是主线程往下运行了
    int size1 = list.size();
    for (int i = 0; i < size1; i++) {
        //得到返回结果的List<User>
        Future<List<User>> future = list.get(i);
        List<User> userList = null; 
        //这里要自旋等待,等上面的线程任务返回成功才得到结果
        //为什么要自旋等待,因为这里是主线程和子线程并行,子线程可能还没有运行完,主线程就到这一步了,所以要自旋,等待子线程运行完,返回任务结果。
         while (true) {
            if (future.isDone()) {
                try {
                    userList = future.get();
                    log.info("这是第" + i + "返回结果,总共有" + userList.size() + "个结果");
                    long currentTimeMillis = System.currentTimeMillis();
                    //userService.saveBatch(userList);可以将数据录入数据库中
                    log.info("使用的时间是==>" + (System.currentTimeMillis() - currentTimeMillis));
                    break;
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


//这个失败的案例值得记录下来
@Test
void threadTest8() throws Exception {
    //这个是接收Future的线程安全的集合
    List<Future<List<User>>> list = Collections.synchronizedList(new ArrayList<>());
    CyclicBarrier cyclicBarrier = new CyclicBarrier(8, () -> {
        log.info("你好啊,这里是CyclicBarrier线程");
        int size1 = list.size();
        // 这里因为是cyclicBarrier.await();满足减到0的条件的同时,开启一个线程,而任务没有结果没有返回出去,所以future.isDone()是false
        for (int i = 0; i < size1; i++) {
            Future<List<User>> future = list.get(i);
            //future.isDone()会判断future的任务是否返回成功
            log.info("当前线程=>" + Thread.currentThread().getName() + "===future.isDone()==>"+future.isDone());
        }
    });
    //开启8个线程进行测试
    for (int i = 0; i < 8; i++) {
        //这里是采用线程池的方式启动线程,submit就是启动callable接口,会有返回值
        Future<List<User>> future = executor.submit((() -> {
            List<User> userList = Collections.synchronizedList(new ArrayList<>());
            //模拟获取大量数据
            for (int j = 0; j < 2000; j++) {
                //这里是一个获得数据的方法,读者可以自己定义
                User user = asyncTaskService.getUser();
                userList.add(user);
            }
            log.info("当前线程=>" + Thread.currentThread().getName() + "===正在获取数据");
            //这里设置阻塞,只有8个线程都完成了上面的循环,来到这里,进行减一,只有减到0,才能往后继续,否则先到的线程就会继续阻塞
            cyclicBarrier.await();
            return userList;
        }));
        list.add(future);
    }
    //主线程阻塞,如果不阻塞,可能子线程还没开出来,主线程就已经结束任务了,程序结束
    Thread.sleep(2000);

}

Semaphore

Semaphore也是一个线程同步的辅助类,可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数

这个好比是生活的车道一样,给你规定了多少个车道就一次性只能走这么多车,多余的车就在后面的阻塞等待,等前面有车叉出去了,你就可以进去那个车道上

Semaphore的主要方法摘要:

void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。(增加)

void release():释放一个许可,将其返回给信号量。(减少)

int availablePermits():返回此信号量中当前可用的许可数。

boolean hasQueuedThreads():查询是否有线程正在等待获取。

用法

 public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        
        //信号量,只允许 3个线程同时访问
        Semaphore semaphore = new Semaphore(3);

        for (int i=0;i<10;i++){
            final long num = i;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        //获取许可
                        semaphore.acquire();
                        //执行
                        System.out.println("Accessing: " + num);
                        Thread.sleep(new Random().nextInt(5000)); // 模拟随机执行时长
                        //释放
                        semaphore.release();
                        System.out.println("Release..." + num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值