多线程的使用(springboot)

预备知识

业务使用多线程的原因

  • 目的是面对高并发的时候,提高运行速度

场景一:

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

场景二:

需要知道一个任务的执行进度,比如我们常看到的进度条,实现方式可以是在任务中加入一个整型属性变量(这样不同方法可以共享),任务执行一定程度就给变量值加1,另外开一个线程按时间间隔不断去访问这个变量,并反馈给用户。总之使用多线程就是为了充分利用cpu的资源,提高程序执行效率,当你发现一个业务逻辑执行效率特别低,耗时特别长,就可以考虑使用多线程。

问题:不过CPU执行哪个线程的时间和顺序是不确定的,即使设置了线程的优先级,因此使用多线程的风险也是比较大的,会出现很多预料不到的问题,一定要多熟悉概念,多构造不同的场景去测试才能够掌握!

项目中可以通过:

@Order()
设置运行的优先级,数字越小,级别越高

FutureTask介绍

参考: (101条消息) FutureTask详解_索码理的博客-CSDN博客_futuretask

线程池为什么要使用阻塞队列

阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait 状态,释放 cpu 资源,当队列中有任务时才唤醒对应线程从队列中取出消息进行执行。
使得在线程不至于一直占用cpu资源。(线程执行完任务后通过循环再次从任务队列中取出任务进行执行,代码片段如:while (task != null || (task = getTask()) != null) {})。

不用阻塞队列也是可以的,不过实现起来比较麻烦而已,有好用的为啥不用呢

Spring线程池

介绍:

Spring 通过任务执行器(TaskExecutor)来实现多线程和并发编程,使用 ThreadPoolTaskExecutor 实现一个基于线程池的TaskExecutor,
还得需要使用 @EnableAsync 开启异步,并通过在需要的异步方法那里使用注解@Async声明是一个异步任务
Spring 已经实现的异常线程池:

  • SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
  • SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方
  • ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类
  • SimpleThreadPoolTaskExecutor:是Quartz的 SimpleThreadPool 的类。线程池同时被quartz和非quartz使用,才需要使用此类
  • ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对 java.util.concurrent.ThreadPoolExecutor 的包装

同步使用

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
FutureTask<List> commentCallable = new FutureTask<>(GrouponRulesCallable);
Future<Map> submit = executor.submit(commentCallable);
Map map = submit.get();

异步的使用:

[(101条消息) SpringBoot实现多线程_喜欢前端的后端MelodyJerry的博客-CSDN博客_springboot实现多线程](//blog.csdn.net/weixin_43438052/article/details/116108860#:~:text=SpringBoot实现 多线程 1 SpringBoot通过 任务执行器TaskExecutor 来实现多线程和并发编程。 2 使用,%40EnableAsync 开启对 异步任务 的支持,并通过在实际执行的Bean中的方法使用 %40Async注解 来声明这是一个 异步任务 。)

JUC 的线程池

并发下使用同步线程

具体代码参考: litemall示例代码

  • 声明
    private final static ArrayBlockingQueue<Runnable> WORK_QUEUE= new ArrayBlockingQueue<>(9);
    // handler – 由于达到线程边界和队列容量而阻塞执行时使用的处理程序
    private final static RejectedExecutionHandler HANDLER=new ThreadPoolExecutor.CallerRunsPolicy();

    private static ThreadPoolExecutor executorService = new ThreadPoolExecutor(16, 16, 1000, TimeUnit.MILLISECONDS, WORK_QUEUE, HANDLER);

  • 使用,通过 get() 方法获取他的返回值
//        商品属性
        Callable<List> listCallable  =()->goodsAttributeService.getGoodsAttributeList(litemallGoods.getGoodsSn());

//        商品规格
        Callable<Object> specificationCallable =()->  specificationService.getGoodsSpecification(litemallGoods.getGoodsSn());


        FutureTask<List> goodsAttributeListTask = new FutureTask<>(listCallable);
        FutureTask<Object> objectCallableTask = new FutureTask<>(specificationCallable);
        
        executorService.submit(goodsAttributeListTask);
        executorService.submit(objectCallableTask);