多线程杀手锏---countDownLatch&&CyclicBarrier

多线程是我们学习路上必不可少的一块拦路石,撬开这块石头,就需要掌握好JUC。JUC下的一些并发同步特性在之前的文章里也写过,具体如下:
多线程基础知识

什么是JMM以及volatile的特性
详谈CAS以及ABA问题

线程池以及阻塞队列

AQS的了解,对于AQS是一个比较重要的知识点,下面这几篇文章可以帮助你通俗易懂的了解AQS是如何在底层执行工作的
谈谈你对AQS的了解

AQS的核心原理分析

Java 并发高频面试题:聊聊你对 AQS 的理解?

总的来说AQS在并发里相当于人的大脑,里边一个state,一个当前得锁的线程,一个FIFO的阻塞队列就把线程的安全解决了,

然后接下来说今天的主题 多线程下countDownLatch和CyclicBarrier是如何并发执行,以及它们之间的区别:
CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同;另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的
在这里插入图片描述在这里插入图片描述看完后你会发现CountDownLatch和CyclicBarrier两者的await方法是会阻塞当前线程的,因此如果在线程池中使用CyclicBarrier,如果线程池的core大小小于parties,线程池中的线程遍一直处于阻塞状态,相反CountDownLatch的countDown()在执行后,完美退出当前线程,但是在开启调用CountDownLatch.await()的时候,需要在子线程中,否则就阻塞了当前线程。

上面的如果看的不太了解,则可以看看接下来的这两个具体代码对比:

CyclicBarrier:

1、CyclicBarrier:一个同步辅助类,用于协调多个子线程,让多个子线程在这个屏障前等待,直到所有子线程都到达了这个屏障时,再一起继续执行后面的动作。

2、使用场景举例:
年末公司组织团建,要求每一位员工周六上午8点【自驾车】到公司门口集合,然后【自驾车】前往目的地。
在这个案例中,公司作为主线程,员工作为子线程。

package com.test.spring.support;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/** 
 * @author  javaloveiphone
 * @date 创建时间:2017年1月25日 上午10:59:11 
 * @Description: 
 */
public class Company {
    public static void main(String[] args) throws InterruptedException {

        //员工数量
        int count = 5;
        //创建计数器
        CyclicBarrier barrier = new CyclicBarrier(count+1);

        //创建线程池,可以通过以下方式创建
        //ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1,1,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(count));
        ExecutorService threadPool =  Executors.newFixedThreadPool(count);

        System.out.println("公司发送通知,每一位员工在周六早上8点【自驾车】到公司大门口集合");
        for(int i =0;i<count ;i++){
            //将子线程添加进线程池执行
            threadPool.execute(new Employee(barrier,i+1));
            Thread.sleep(10);
        }
        try {
            //阻塞当前线程,直到所有员工到达公司大门口之后才执行
            barrier.await();
            Thread.sleep(10);
            // 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
            //latch.await(long timeout, TimeUnit unit)
            System.out.println("所有员工已经到达公司大门口,公司领导一并【自驾车】同员工前往活动目的地。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }finally{
            //最后关闭线程池,但执行以前提交的任务,不接受新任务
            threadPool.shutdown();
            //关闭线程池,停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
            //threadPool.shutdownNow();
        }
    }
}

//分布式工作线程
class Employee implements Runnable{

    private CyclicBarrier barrier;
    private int employeeIndex;

    public Employee(CyclicBarrier barrier,int employeeIndex){
        this.barrier = barrier;
        this.employeeIndex = employeeIndex;
    }

    @Override
    public void run() {
        try {
            System.out.println("员工:"+employeeIndex+",正在前往公司大门口集合...");
            Thread.sleep(10*employeeIndex);
            System.out.println("员工:"+employeeIndex+",已到达。");
            barrier.await();
            Thread.sleep(10);
            System.out.println("员工:"+employeeIndex+",【自驾车】前往目的地");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述子线程执行了await()方法,必须等待其它所有子线程执行await()方法之后,才能一起继续后续的(await后main的)工作,就像上面的例子,所有自驾车必须都到达公司大门口之后,才能一起继续各自自驾车前往目的地。
但,主线程await()之后的工作与子线程await()之后的工作是不受影响的,只要所有的子线程执行了await()方法,主线程此时就可以后续的工作了,不必管子线程await()方法后续工作的情况。

可以看到的是只要都到达了屏障以后,各自执行自己,并不按照之前到达的顺序执行。

CountDownLatch

1、CountDownLatch:一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。

2、ThreadPoolExecutor/ExecutorService:线程池,使用线程池可以复用线程,降低频繁创建线程造成的性能消耗,同时对线程的创建、启动、停止、销毁等操作更简便。

3、使用场景举例:
年末公司组织团建,要求每一位员工周六上午8点到公司门口集合,统一乘坐公司所租大巴前往目的地。
在这个案例中,公司作为主线程,员工作为子线程。

package com.test.thread;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/** 
 * @author  javaloveiphone
 * @date 创建时间:2017年1月25日 上午10:59:11 
 * @Description: 
 */
public class Company {
    public static void main(String[] args) throws InterruptedException {

        //员工数量
        int count = 5;
        //创建计数器
        //构造参数传入的数量值代表的是latch.countDown()调用的次数
        CountDownLatch latch = new CountDownLatch(count);

        //创建线程池,可以通过以下方式创建
        //ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1,1,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(count));
        ExecutorService threadPool =  Executors.newFixedThreadPool(count);

        System.out.println("公司发送通知,每一位员工在周六早上8点到公司大门口集合");
        for(int i =0;i<count ;i++){
            //将子线程添加进线程池执行
            Thread.sleep(10);
            threadPool.execute(new Employee(latch,i+1));
        }
        try {
            //阻塞当前线程,直到所有员工到达公司大门口之后才执行
            latch.await();
            // 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
            //latch.await(long timeout, TimeUnit unit)
            System.out.println("所有员工已经到达公司大门口,大巴车发动,前往活动目的地。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            //最后关闭线程池,但执行以前提交的任务,不接受新任务
            threadPool.shutdown();
            //关闭线程池,停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
            //threadPool.shutdownNow();
        }
    }
}

//分布式工作线程
class Employee implements Runnable{

    private CountDownLatch latch;
    private int employeeIndex;

    public Employee(CountDownLatch latch,int employeeIndex){
        this.latch = latch;
        this.employeeIndex = employeeIndex;
    }

    @Override
    public void run() {
        try {
            System.out.println("员工:"+employeeIndex+",正在前往公司大门口集合...");
            Thread.sleep(10);
            System.out.println("员工:"+employeeIndex+",已到达。");
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            //当前计算工作已结束,计数器减一
            latch.countDown();  
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //执行coutDown()之后,继续执行自己的工作,不受主线程的影响
            System.out.println("员工:"+employeeIndex+",吃饭、喝水、拍照。");
        }
    }
}

在这里插入图片描述每一个员工到达之后,执行countDown()方法,直到所有员工到达之后,计数器为0,主线程才会继续执行。

但子线程执行了countDown()方法,之后会继续自己的工作,比如上面的【吃饭、喝水、拍照】,是不受主线程是否阻塞、其它线程是否已经执行countDown()方法的影响的。

CyclicBarrier与CountDownLatch的区别:

1)、构造两者对象传入的参数不一样:构造CyclicBarrier比构造CountDownLatch的参数大了1,原因是构造CyclicBarrier的数量表示的是调用await()的次数,构造CountDownLatch的数量表示的是调用countDown()的次数;

2)、子线程调用了barrier.await()之后,必须等待所有子线程都完成barrier.await()调用后才能一起继续后续自己的工作,而子线程调用latch.countDown()之后,会继续子线程自己的工作,不用等待其它子线程latch.countDown()调用情况。

3)、CyclicBarrier可以循环使用,而CountDownLatch不是循环使用的。

通俗一点就是:
countDownLatch 类似于学校的 4*100接力赛;一共count为4次(4圈),当一个人跑完以后,他就可以休息,喝水了(继续执行子线程),其他人也一样,等所有人都跑完以后,count=0了,裁判宣布结果(main线程)。

CyclicBarrier:有一天同学们上课了,班里老师来的早,等待着那些同学陆陆续续的到来。老师讲课(main线程),同学们听课,记笔记(子线程)。同学们还没有来全,老师不能讲课(main线程被阻塞了),其他在教室的学生也不能记笔记,因为老师还没开始讲呢(子线程在屏障前等待),当所有人都到了以后,开始各干各的,老师讲课,他们在下边记笔记,传纸条等等等。互不打扰。

再说一下谷歌插件的ListeningExecutorService的使用方法

由于普通的线程池,返回的Future,功能比较单一;Guava 定义了 ListenableFuture接口并继承了JDK concurrent包下的Future 接口,ListenableFuture 允许你注册回调方法(callbacks),在运算(多线程执行)完成的时候进行调用。

1.使用方法如下:

1.创建线程池
2.装饰线程池
3.任务处理
4.回调函数处理
5.所有任务完成后处理

会执行回调方法, 执行成功的,执行不成功的都走方法
在这里插入图片描述执行成功截图:

在这里插入图片描述

将等待失效时间修改如下:
在这里插入图片描述
线程池等待5秒,五秒后自动结束里面的线程。
在这里插入图片描述执行了shutdownNow方法,正在执行的被中断了,未被执行的因为本例是基础的Callable所有没能接收返回的Runnable线程,线程继承Runnable可以接收返回的未被执行的那些线程。

上述案例的全部代码如下:

在这里插入图片描述

pom依赖

 <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>18.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.15</version>
        </dependency>
        
    </dependencies>


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Callable;

/**
 * @author Shuyu.Wang
 * @package:com.ganinfo.test
 * @className:
 * @description:
 * @date 2018-10-28 19:35
 **/
public class AuthCallable implements Callable {

    private AuthType authType;
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    @Override
    public Object call() throws Exception {
        if ("1".equals(authType.getType())){
            System.out.println("方式一"+authType.getName()+"授权start");
            Thread.sleep(8000);
            System.out.println("方式一授权end");
            return authType.getName();
        }
        if ("2".equals(authType.getType())){
            System.out.println("方式二"+authType.getName()+"授权start");
            Thread.sleep(7000);
            System.out.println("方式二授权end");
            return authType.getName();
        }
        if ("3".equals(authType.getType())){
            System.out.println("方式三"+authType.getName()+"授权start");
            Thread.sleep(5000);
            System.out.println("方式三授权end");
            return authType.getName();
        }
        if ("4".equals(authType.getType())){
            System.out.println("方式四"+authType.getName()+"授权start");
            Thread.sleep(3000);
            System.out.println("方式四授权end");
            return authType.getName();
        }
        if ("5".equals(authType.getType())){
            System.out.println("方式五"+authType.getName()+"授权start");
            Thread.sleep(1000);
            System.out.println("方式五授权end");
            return authType.getName();
        }
        return null;
    }

    public void setAuthType(AuthType authType) {
        this.authType = authType;
    }
}

import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * @author Shuyu.Wang
 * @package:com.ganinfo.test
 * @className:
 * @description:
 * @date 2018-10-28 19:41
 **/
public class AuthService {
    public static void main(String[] args) {
            long start=System.currentTimeMillis();
            final List<String> list = new ArrayList<>();
            int count=5;
            try {
                final CountDownLatch countDownLatch = new CountDownLatch(count);
                ExecutorService executorService = Executors.newFixedThreadPool(8);
                ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(executorService);
                for (int i = 1; i <= count; i++) {
                    AuthCallable authCallable = new AuthCallable();
                    AuthType authType = new AuthType();
                    authType.setType(String.valueOf(i));
                    authType.setName(String.valueOf(i) + "名称");
                    authCallable.setAuthType(authType);
                    ListenableFuture listenableFuture = listeningExecutorService.submit(authCallable);
                    Futures.addCallback(listenableFuture, new FutureCallback<String>() {
                        @Override
                        public void onSuccess(String name) {
                            System.out.println("授权结果" + name);
                            list.add(name);
                            countDownLatch.countDown();
                        }
                        @Override
                        public void onFailure(Throwable throwable) {
                            countDownLatch.countDown();
                            System.out.println("处理出错:"+throwable);
                        }
                    });
                }
                try {
                    executorService.shutdown();
                    //shutdown调用后,不可以再submit新的task,已经submit的将继续执行。
                    if (!countDownLatch.await(5, TimeUnit.MINUTES)) {
                        System.out.println("超时的时候向线程池中所有的线程发出中断");
                        // 超时的时候向线程池中所有的线程发出中断(interrupted)。
                        executorService.shutdownNow();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //shutdownNow试图停止当前正执行的task,并返回尚未执行的task的list
                    //由于本例中继承的是Callable,所以无法接收返回的Runable接口
                    List<Runnable> listRun =executorService.shutdownNow();
                }
                System.out.println("执行结果" + list.toString());
                long end=System.currentTimeMillis();
                System.out.println("用时" + (end-start));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
}

import lombok.Data;

/**
 * @author Shuyu.Wang
 * @package:com.ganinfo.test
 * @className:
 * @description:
 * @date 2018-10-28 19:34
 **/
@Data
public class AuthType {
    private String type;
    private String name;
}

一句话说明白shutdown和shutdownNow的区别

shutdown只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断。而shutdownNow则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回。

参考文章:
Java并发编程–CountDownLatch配合线程池
java多线程CyclicBarrier使用示例,让线程起步走

java多线程CountDownLatch及线程池ThreadPoolExecutor/ExecutorService使用示例

https://blog.csdn.net/weixin_39800144/article/details/82776523

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌晨里的无聊人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值