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());
}
}
运行结果:
3、如果只开启@EnableAsync注解,但没有配置对应线程池
a、在 @Async 使用时指定对应的线程池,
/** 这样可以指定对应线程池取线程来工作 */
@Async("asyncTaskExecuter")
这样还可以在不同的任务之间配置成多个线程池,这样手动指定也有很好的好处。
b、在 @Async 使用时没有配置线程池(最好不直接使用,要么提前配置线程池,要么这里指定线程池)
@Async()
如果没有配置对应线程池,也没有手动指定线程池,那么默认使用SimpleAsyncTaskExecutor。
SimpleAsyncTaskExecutor,不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程,没有最大线程数设置。并发大的时候会产生严重的性能问题。
还有个 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线程池执行器。
ThreadPoolExecutor构造方法
ThreadPoolExecutor。 |
ThreadPoolExecutor。 |
ThreadPoolExecutor。 |
ThreadPoolExecutor。 |
参数说明:
corePoolSize: 核心线程池大小;
maximumPoolSize:最大线程池大小;
keepAliveTime:保持活动的时间;
workQueue:执行前用于保持任务的队列;
threadFactory:用于创建新线程的线程工厂;
handler:由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序;
建议使用6个参数这个构造方法哦;或者说只能使用这个构造方法;
处理流程:
包括后面的ThreadPoolTaskExecutor时这个的封装,也时这个处理流程。
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()方法,可以有返回值。
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
}
案例代码
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() );
三、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;
}
上面这个是案例,其实在第一部分就已经使用了。