Spring usa elegantemente pools de threads e aceita mensagens assíncronas de maneira elegante

Introdução às roscas do cortador de biscoitos

A maioria dos documentos técnicos que introduzem threads são da cv. Hoje vamos fazer algo diferente.

  • Grupo de discussão

Criar threads por conta própria envolve a criação e destruição de threads, o que consome muitos recursos e é desnecessário. Use o ThreadPoolExecutor do jdk, use esta classe para criar um pool de threads, o uso específico dessa classe não está muito envolvido aqui

  • piscina de fios na primavera

Várias implementações de pool de threads bem definidas são encapsuladas no spring (implementando TaskExecutor)

  1. SyncTaskExecutor: Executa tarefas de forma síncrona no thread atual do chamador
  2. SimpleAsyncTaskExecutor criará um novo thread para cada tarefa e parará depois de executar o thread
  3. ThreadPoolTaskExecutor , o mais comumente usado, encapsula ThreadPoolExecutor, a tarefa é lançada e é finalizada

uso real

  • O fluxo de trabalho geral do pool de threads (compreensão pessoal, erros corretos)
  1. Inicialize os threads de acordo com o tamanho de CorePoolSize, e esses threads não serão destruídos (você pode definir o parâmetro AllowCoreThreadTimeOut como true para destruir os threads principais também)
  2. Quando mais do que o thread principal, coloque a tarefa multithread no QueueCapacity
  3. Quando o QueueCapacity está cheio, um novo thread é criado de acordo com a configuração de MaxPoolSize, que não excederá essa configuração no máximo. E esses tópicos serão destruídos após o tempo de setKeepAliveSeconds
  4. Quando a thread atingir MaxPoolSize, ela rejeitará o processo de acordo com a política RejectedExecutionHandler. A mais comumente usada é CallerRunsPolicy: quando a fila da thread estiver cheia, ela será entregue ao chamador para execução, que geralmente é entregue ao main fio para processamento.
  • Crie uma classe de configuração (entregue ao gerenciamento de primavera)

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.EnableAsync;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    
    import java.util.concurrent.Executor;
    import java.util.concurrent.ThreadPoolExecutor;
    
    /**
     * 多线程配置类
     *
     * @author ActStrady
     * @date 2020/7/30
     */
    @Configuration
    @EnableAsync
    public class AsyncConfiguration {
        /**
         * 多线程配置
         *
         * @return 线程
         */
        @Bean("taskExecutor")
        public Executor asyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            // 核心线程数5:线程池创建时候初始化的线程数
            executor.setCorePoolSize(5);
            // 最大线程数5:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
            executor.setMaxPoolSize(10);
            // 缓冲队列500:用来缓冲执行任务的队列
            executor.setQueueCapacity(500);
            // 允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
            executor.setKeepAliveSeconds(60);
            // 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
            executor.setThreadNamePrefix("DailyAsync-");
            // 设置拒绝策略. 替换默认线程池,线程队列满了以后交给调用者执行,也就是同步执行 共四种策略
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            // 等待所有任务结束后再关闭线程池
            executor.setWaitForTasksToCompleteOnShutdown(true);
            executor.initialize();
            return executor;
        }
    }
    复制代码

    A anotação @EnableAsync é usada para habilitar a configuração do pool de threads

  • 关于几个核心配置的解释 CorePoolSize:核心线程数量 MaxPoolSize:最大线程数量,超过这个就会执行拒绝策略 QueueCapacity:任务队列,用来缓存任务 keepAliveSeconds: 除核心线程外的普通线程的超时回收时间 RejectedExecutionHandler:拒绝策略,当线程打满且没有销毁的时候执行的拒绝策略 拒绝策略种类: 1. AbortPolicy,默认的处理方式,简单粗暴,丢弃任务并抛出RejectedExecutionException异常 2. DiscardPolicy:丢弃任务,但是不抛出异常 3. DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程 4. CallerRunsPolicy:由调用线程处理该任务(一般主线程)一般使用该策略

  • 关于线程数量设置 根据java并发编程实战得到下面公式

    CPU 密集型任务:这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1
    I/O 密集型任务:线程数=N(CPU核数)* 2
    混合任务:线程数=N(CPU核数)*(1+WT(线程等待时间)/ST(线程时间运行时间))
    复制代码

    翻阅很多资料并不能得出一个最合理的配置或者说完全没有一个确定的方案,到底核心线程数按照这个来设置还是最大线程数按照这个来设置?基本上各自说各自的,先把这块当做一个未知吧 ,以目前的理解来说我个人认为还是最大线程数按照这个设置,这边有一篇美团的把参数动态设置,这几个参数还得按照业务来确定。

  • 注解使用

  1. 无返回值
        @Async("taskExecutor")
        public void test() {
        }
    复制代码
    对于无返回值的就很简单,使用上边注入(@Bean("taskExecutor"))的线程配置名称来直接使用(@Async("taskExecutor"))
  2. 有返回值 有返回值的情况我们又可以使用CompletableFuture或者Future来处理,关于这两种方法的区别,我引用廖老师的一段话来说明下

    使用Future获得异步执行结果时,要么调用阻塞方法get(),要么轮询看isDone()是否为true,这两种方法都不是很好,因为主线程也会被迫等待。

    从Java 8开始引入了CompletableFuture,它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。
    • Future 处理
       	@Async("taskExecutor")
       	// 泛型就是你要返回的结果类型
          public Future<String> test() {
          	// 结果就是你返回的结果
          	return new AsyncResult<>("结果");
          }
      复制代码
      就像上边廖老师说的调用阻塞方法get(),要么轮询看isDone()是否为true,这样的话无法直接表述多个Future 结果之间的依赖性。
    • CompletableFuture处理

liurio.github.io/2019/12/17/…

本文已参与「新人创作礼」活动,一起开启掘金创作之路

Acho que você gosta

Origin juejin.im/post/7078244286088626207
Recomendado
Clasificación