springboot异步,异步线程池,ThreadPoolExecutor和ThreadPoolTaskExecutor之间的介绍


一、springboot异步任务

1、使用 @EnableAsync 开启异步

EnableAsyncConfig.java

package com.imddy.layuiadmin.config;

import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionHandler;

@Configuration
@EnableAsync
public class EnableAsyncConfig implements AsyncConfigurer {
    private static final Logger logger = LoggerFactory.getLogger(EnableAsyncConfig.class);

    /** 设置默认Async异步线程池 */
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor asyncTaskExecutor = new ThreadPoolTaskExecutor();
        //核心池大小
        asyncTaskExecutor.setCorePoolSize(20);
        //最大线程数
        asyncTaskExecutor.setMaxPoolSize(50);
        //队列程度
        asyncTaskExecutor.setQueueCapacity(100);
        //线程前缀名称
        asyncTaskExecutor.setThreadNamePrefix("asyncTaskExecutor--thread---");
        /**
         * 设置拒绝策略rejection-policy:当pool已经达到max size的时候,如何处理新任务
         * CallerRunsPolicy():用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。交由调用方线程运行,比如 main 线程。
         * AbortPolicy():直接抛出异常。用于被拒绝任务的处理程序,它将抛出RejectedExecutionException。
         * DiscardPolicy():直接丢弃。用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
         * DiscardOldestPolicy():丢弃队列中最老的任务。用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。
         */
        asyncTaskExecutor.setRejectedExecutionHandler((RejectedExecutionHandler) new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        asyncTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        asyncTaskExecutor.initialize();
        return asyncTaskExecutor;
    }


    /** 异步线程异常处理 */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(Throwable ex, Method method, Object... params) {
                logger.error("asyncTask异步线程池异常获取的error=================="+ex.getMessage());
                logger.error("asyncTask异步线程池异常获取的Method=================="+method.getName());
                logger.error("asyncTask异步线程池异常获取的Object=================="+params);
            }
        };
    }

}


2、使用 @Async 使对应方法异步执行

Test001Serivce.java

package com.imddy.layuiadmin.service;

import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

@Service
public class Test001Serivce {
    private static final Logger logger = LoggerFactory.getLogger(Test001Serivce.class);

    @SneakyThrows
    @Async
    public void test001() {
        logger.info("Test001Serivce现在时间:" + LocalDateTime.now());
        TimeUnit.SECONDS.sleep(5L);
        logger.info("Test001Serivce现在时间:" + LocalDateTime.now());
    }

}

运行结果:

springboot异步,异步线程池,ThreadPoolExecutor和ThreadPoolTaskExecutor之间的介绍_spring


3、如果只开启@EnableAsync注解,但没有配置对应线程池

a、在 @Async 使用时指定对应的线程池,

/** 这样可以指定对应线程池取线程来工作 */
@Async("asyncTaskExecuter")

这样还可以在不同的任务之间配置成多个线程池,这样手动指定也有很好的好处。


b、在 @Async 使用时没有配置线程池(最好不直接使用,要么提前配置线程池,要么这里指定线程池)

@Async()

如果没有配置对应线程池,也没有手动指定线程池,那么默认使用SimpleAsyncTaskExecutor。

SimpleAsyncTaskExecutor,不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程,没有最大线程数设置。并发大的时候会产生严重的性能问题。

springboot异步,异步线程池,ThreadPoolExecutor和ThreadPoolTaskExecutor之间的介绍_spring_02

还有个 SimpleThreadPoolTaskExecutor。



二、ThreadPoolExecutor(JDK自带的线程池【执行器】)

ThreadPoolExecutor,位于包 java.util.concurrent 下。

Executor,执行器接口,也是在该包下,里面只有一个方法 execute(Runnable command) 用来执行任务;

ExecutorService, 这个类似线程池接口,他 extends Executer接口而来,他由invokeall和submit等方法来提交任务(invoke同步,submit异步)。

ScheduledExecutorService, 这个接口也是 extends ExecutorService接口而来,他比ExecutorService多了可以调度接口(如schedule等各种调度方法)。

ThreadPoolExecutor, ThreadPoolExecutor extends AbstractExecutorService而来,所有也就是ThreadPoolExecutor线程池执行器。


springboot异步,异步线程池,ThreadPoolExecutor和ThreadPoolTaskExecutor之间的介绍_spring_03

springboot异步,异步线程池,ThreadPoolExecutor和ThreadPoolTaskExecutor之间的介绍_异步线程_04


ThreadPoolExecutor构造方法

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)

 ThreadPoolExecutor

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler

 ThreadPoolExecutor

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory

 ThreadPoolExecutor

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler

 ThreadPoolExecutor

参数说明:

corePoolSize: 核心线程池大小;

maximumPoolSize:最大线程池大小;

keepAliveTime:保持活动的时间;

workQueue:执行前用于保持任务的队列;

threadFactory:用于创建新线程的线程工厂;

handler:由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序;

建议使用6个参数这个构造方法哦;或者说只能使用这个构造方法;

处理流程:

包括后面的ThreadPoolTaskExecutor时这个的封装,也时这个处理流程。

springboot异步,异步线程池,ThreadPoolExecutor和ThreadPoolTaskExecutor之间的介绍_异步线程_05



Executors工具类,用于快速生成线程池

创建单个线程池,newSingleThreadExecutor(),线程池中只有一个线程。

public static ExecutorService newSingleThreadExecutor() {
   return new FinalizableDelegatedExecutorService
      (new ThreadPoolExecutor(1, 1, 0L,TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue<Runnable>()));
    }

该方法创建单线程的线程池(从源码中可以看到核心线程数是1,最大线程数也是1)!

工作队列采用的是无界的LinkedBlockingQueue阻塞队列。支持先提交的先执行!

如果核心线程异常的话,则创建一个线程去顶替核心线程(但始终保持单线程)

如果阻塞队列中任务太多,单线程处理不完则会引发OOM


创建固定大小的线程池,newFixedThreadPool(int nThreads)

public static ExecutorService newFixedThreadPool(int nThreads) {
  return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,
          new LinkedBlockingQueue<Runnable>());
    }

从源码中我们可以看出:该方法会创建一个固定大小的线程池。

核心线程数和最大线程数一致就表明:

当接受一个任务后就创建一个线程直至达到最大线程数。此时会将任务加入工作队列中

工作队列采用的是无界的阻塞队列,支持先提交的先执行。若处理不过来会引发OOM


创建缓存线程池,newCachedThreadPool (),线程池的数量不固定,可以根据需求自动的更改数量。

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>());
    }

创建一个可缓存的线程池,如果线程池的大小超多处理任务的线程,

那么就会回收空闲线程。当先提交一个线程时便会创建一个线程去处理。

该线程池的最大线程数默认为Integer.MAX_VALUE

内部采用SynchronousQueue队列,该队列要求只有线程获取任务的话才能加入队列中


创建固定大小的可执行调度任务的线程池,newScheduledThreadPool(int corePoolSize),可以延迟或定时的执行任务。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    //创建源码:
            super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());

该线程池创建的是支持定时任务的线程池.核心线程数指定,最大线程数为Interger.MAX_VALUE

内部使用的是:DelayedWorkQueue无界优先级阻塞队列。要求元素都实现 Delayed 接口



Future接口,Callable接口,Runnable接口,FutureTask实现类

Rnnable接口,这个是正常java的多线程任务需要实现的接口。

Callable接口,这个也是多线程任务需要的接口,但是他有返回值。

Runnable是JDK 1.0的时候提出的多线程实现接口,而Callable是在JDK 1.5之后提出的。

java.lang.Runnable接口中只提供有一个run()方法,并且没有返回值。

java.util.concurrent.Callable接口提供有call()方法,可以有返回值。

springboot异步,异步线程池,ThreadPoolExecutor和ThreadPoolTaskExecutor之间的介绍_异步线程_06

Future接口,这个接口是异步计算返回的结果,他可以有取消,或者通过get来获取结果。

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);     //取消
    boolean isCancelled();      //判断是否取消
    boolean isDone();      //判断是否完成
    V get() throws InterruptedException, ExecutionException;      //获取异步结果
    V get(long timeout, TimeUnit unit)        //获取异步结果,超时返回超时异常
        throws InterruptedException, ExecutionException, TimeoutException;
}


FutureTask类,实现了RunnableFuture接口,RunnableFuture接口扩展了Runnable, Future<V>这2个接口,可以理解为FutureTask只能传入Callable实例,如果是Runnable那么他返回的Result为自己传入的对象。

 public FutureTask(Callable<V> callable) {

       if (callable == null)

           throw new NullPointerException();

       this.callable = callable;

       this.state = NEW;       // ensure visibility of callable

   }

  public FutureTask(Runnable runnable, V result) {

       this.callable = Executors.callable(runnable, result);

       this.state = NEW;       // ensure visibility of callable

   }

springboot异步,异步线程池,ThreadPoolExecutor和ThreadPoolTaskExecutor之间的介绍_异步线程_07

案例代码

new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"执行Runnable");
        }).start();

        FutureTask<String> task = new FutureTask<>(()->{
            System.out.println(Thread.currentThread().getName()+"使用Callable接口");
            return "Callable接口返回值";
        });
        new Thread(task).start();
        System.out.println("Callable返回值:" + task.get() );

        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                return String.valueOf( new Random().nextInt(99999) );
            }
        };
        FutureTask<String> futureTask = new FutureTask<>(callable);
        futureTask.run();
        System.out.println("FutureTask返回值:" + futureTask.get() );

springboot异步,异步线程池,ThreadPoolExecutor和ThreadPoolTaskExecutor之间的介绍_spring_08



三、ThreadPoolTaskExecutor线程池

这个是线程池spring对ThreadPoolExecutor的2次封装。

@Bean
public Executor getAsyncExecutor() { 
        ThreadPoolTaskExecutor asyncTaskExecutor = new ThreadPoolTaskExecutor();
        //核心池大小
        asyncTaskExecutor.setCorePoolSize(20);
        //最大线程数
        asyncTaskExecutor.setMaxPoolSize(50);
        //队列程度
        asyncTaskExecutor.setQueueCapacity(100);
        //线程前缀名称
        asyncTaskExecutor.setThreadNamePrefix("asyncTaskExecutor--thread---");
        /**
         * 设置拒绝策略rejection-policy:当pool已经达到max size的时候,如何处理新任务
         * CallerRunsPolicy():用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。交由调用方线程运行,比如 main 线程。
         * AbortPolicy():直接抛出异常。用于被拒绝任务的处理程序,它将抛出RejectedExecutionException。
         * DiscardPolicy():直接丢弃。用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
         * DiscardOldestPolicy():丢弃队列中最老的任务。用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。
         */
        asyncTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        asyncTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        asyncTaskExecutor.initialize();
        return asyncTaskExecutor;
}

上面这个是案例,其实在第一部分就已经使用了。