多线程的简单应用

1、业务场景

  • 目前公司在做一款预测规划海运拼单系统,当用户将所有的数据Review完成后,并且数据也输入完成点击预测按钮。
  • 后台需要拿到根据用户输入的数据以及条件查询数据库的订单,将这些数据组装成指定格式传给算法那边进行求解预测,因为是数学模型在计算;所以需要等待很久,等数学模型求解完成再将具体的结果数据返回回来,由后端进行数据处理展示到前端,并插入数据库。
  • 用户在数学模型那里浪费时间等待而无法做其他动作不合理;故将这里改为多线程操作,前端每个请求都是一次独立的求解,开启线程求解时将main线程返回。用户做其他操作,求解完成会有提示。

1.1、其它场景:

  • 一个业务逻辑有很多次的循环,每次循环之间没有影响,比如验证1万条url路径是否存在,正常情况要循环1万次,逐个去验证每一条URL,这样效率会很低,假设验证一条需要1分钟,总共就需要1万分钟,有点恐怖。这时可以用多线程,将1万条URL分成50等份,开50个线程,每个线程只需验证200条,这样所有的线程执行完是远小于1万分钟的。

2、使用Spring自定义线程池ThreadPoolTaskExecutor

  • 该线程池对ThreadPoolExecutor(JDK自带线程池,这个也是阿里规范中推荐的)进行了很多封装,使用更加简单。

2.1、线程池的工作原理:

  • 前言:新来一个request时,会先判断线程池中线程个数,如果等于了maximunPoolSize,那么这个任务进入工作队列等待(超出队列后会有拒绝策略),主线程会继续执行。
  • 线程池策略corePoolSize:核心线程数;maximunPoolSize:最大线程数。
  • 1、每当有新的任务到线程池时第一步:先判断线程池中当前线程数量是否达到了corePoolSize,若未达到,则新建线程运行此任务,且任务结束后将该线程保留在线程池中,不做销毁处理,若当前线程数量已达到corePoolSize,则进入下一步;
  • 2、第二步:判断工作队列(workQueue)是否已满,未满则将新的任务提交到工作队列中,满了则进入下一步;
  • 3、第三步:判断线程池中的线程数量是否达到了maxumunPoolSize,如果未达到,则新建一个工作线程来执行这个任务,如果达到了则使用饱和策略来处理这个任务。
  • 注意:在线程池中的线程数量超过corePoolSize时,每当有线程的空闲时间超过了keepAliveTime,这个线程就会被终止。直到线程池中线程的数量不大于corePoolSize为止。由第三步可知,在一般情况下,Java线程池中会长期保持corePoolSize个个数的线程。
  • 饱和策略当工作队列满且线程个数达到maximunPoolSize后所采取的策略
  • AbortPolicy:默认策略;新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获。
  • CallerRunsPolicy:既不抛弃任务也不抛出异常,使用调用者所在线程运行新的任务。
  • DiscardPolicy:丢弃新的任务,且不抛出异常。
  • DiscardOldestPolicy:调用poll方法丢弃工作队列队头的任务,然后尝试提交新任务。

2.2、Spring线程池ThreadPoolTaskExecutor在SpringBoot项目中的使用:

  • 默认情况下,Spring将搜索相关的线程池定义:要么在上下文中搜索唯一的TaskExecutor bean,要么搜索名为“taskExecutor”的Executor bean。如果两者都无法解析,则将使用SimpleAsyncTaskExecutor来处理异步方法调用。
  • 下面介绍使用SpringBoot自带注解方式使用线程池:
  • 1、@EnableAsync就可以将配置类加入IOC容器中使用多线程。
  • 2、使用@Async就可以定义一个线程任务。通过spring给我们提供的ThreadPoolTaskExecutor就可以使用线程池。

2.2.1、@EnableAsync (在配置类中配置线程池的各项参数:)

@Configuration
@EnableAsync
public class ExecutorConfig {
    /**
     * 功能描述  自定义线程池,注入到IOC容器bean名称是:dataCollectionExecutor
     *
     * @param
     * @return java.util.concurrent.Executor
     * @author liYuan
     * @date 2022/3/17 16:56
     */
    @Bean("dataHandleExecutor")
    public Executor dataCollectionExecutor() {
        //ThreadPoolExecutor  和  ThreadPoolTaskExecutor
        // 什么区别?  JDK的和Spring里面的
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数量:当前机器的核心数
        executor.setCorePoolSize(
                Runtime.getRuntime().availableProcessors());
        // 最大线程数, 该任务是CPU密集型任务为主体。
        executor.setMaxPoolSize(
                Runtime.getRuntime().availableProcessors()+2);
        //线程池维护线程所允许的空闲时间
        executor.setKeepAliveSeconds(500);
        // 队列大小
        executor.setQueueCapacity(Integer.MAX_VALUE);
        // 线程池中的线程名前缀
        executor.setThreadNamePrefix("hapSolve-");
        // 拒绝策略:直接拒绝
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        // 执行初始化
        executor.initialize();
        LogUtil.log.info("初始化线程池完成:Bean为dataHandleExecutor");
        return executor;
    }
}

2.2.2、使用线程池中的线程。

  • @Async(“dataHandleExecutor”) 那个bean的名称。请添加图片描述

2.3、注解@Async不生效的原因:

  • 最常见的失效原因:
  • 1、被调用方法 和 调用处的代码都处在同一个类,所以只是相当于本类调用,并没有使用代理类 从而@Async并没有产生效果。还是同步方法使用一个主线程。
  • .没有走Spring的代理类。因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,没有经过Spring容器管理。导致该注解失效)
  • 2、异步方法使用注解@Async的返回值只能为void或者Future。
  • 3、异步方法使用static修饰
  • 4、异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类
  • 5、异步方法不能与异步方法在同一个类中
  • 6、类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
  • 7、如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解
  • 8、在Async 方法上标注@Transactional是没用的。 在Async 方法调用的方法上标注@Transactional 有效。
  • 9、调用被@Async标记的方法的调用者不能和被调用的方法在同一类中不然不会起作用!!!!!!!
  • 10、使用@Async时要求是不能有返回值的不然会报错的 因为异步要求是不关心结果的

3、常见线程执行的任务分为两种:CPU密集型任务和IO密集型任务

  • 了解这个概念有助于辅助线程池个数的定义。(我们这个项目的任务是CPU密集型任务)
  • 1、CPU密集型任务(CPU-bound):在一个任务中,主要做计算,CPU持续在运行,CPU利用率高,具有这种特点的任务称为CPU密集型任务。
  • 2、IO密集型任务(IO-bound):在一个任务中,大部分时间在进行I/O操作,由于I/O速度远远小于CPU,所以任务的大部分时间都在等待IO,CPU利用率低,具有这种特点的任务称为IO密集型任务。
  • 所以我们在设计线程池时,应先对执行的任务有个大体分类,然后根据类型进行设置。一般而言,两种任务的线程数设置如下:
  • CPU密集型任务:线程个数为CPU核数。这几个线程可以并行执行,不存在线程切换的开销,提高了cpu的利用率的同时也减少了切换线程导致的性能损耗
  • IO密集型:线程个数为CPU核数的两倍。到其中的线程在IO操作的时候,其他线程可以继续用cpu,提高了cpu的利用率

4、总结

  • 该应用很简单,熟悉一下线程池的基本用法,暂不涉及同步,死锁,线程返回值,并发部分。
  • 主要两个注解(@Async)(@EnableAsync)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值