为了避免线程重复开销和方便线程管理,在开发中应该尽量避免手动创建线程,使用线程池可以规范管理线程。
@Bean("taskExecutor")
public Executor asyncThread(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//设置核心线程数
executor.setCorePoolSize(5);
//设置最大线程数
executor.setMaxPoolSize(100);
//线程空闲等待时间
executor.setKeepAliveSeconds(100);
//设置线程的最大缓存队列
executor.setQueueCapacity(1000);
/**
* 设置拒绝策略
* rejectedExectutionHandler参数字段用于配置绝策略,常用拒绝策略如下
*
* AbortPolicy:用于被拒绝任务的处理程序,它将抛出RejectedExecutionException
*
* CallerRunsPolicy:用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。
*
* DiscardOldestPolicy:用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。
*
* DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
//设置线程组名
executor.setThreadGroupName("async-thread");
return executor;
}
线程池的七大核心参数:
1:核心线程数
2:最大线程数
3:空闲等待时间
4:空闲等待时间单位
5:工作队列
6:线程工厂
7:拒绝策略
一、 corePoolSize 线程池核心线程大小
corePoolSize 是线程池中的一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。
二、maximumPoolSize 线程池最大线程数量
线程池能够容纳同时执行的最大线程数,此值大于等于1。一个任务被提交到线程池以后,首先会找有没有空闲并且存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会放到工作队列中,直到工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。工作队列满,且线程数等于最大线程数,此时再提交任务则会调用拒绝策略。
三、keepAliveTime 多余的空闲线程存活时间
当线程空闲时间达到keepAliveTime值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止。默认情况下:只有当线程池中的线程数大于corePoolSize时keepAliveTime才会起作用,直到线程中的线程数不大于corepoolSIze,
四、unit 空闲线程存活时间单位
keepAliveTime的计量单位
五、workQueue 工作队列
任务被提交给线程池时,会先进入工作队列,任务调度时再从工作队列中取出。
常用工作队列有以下几种
-
ArrayBlockingQueue(数组的有界阻塞队列)
ArrayBlockingQueue 在创建时必须设置大小,按FIFO排序(先进先出)。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。 -
LinkedBlockingQueue(链表的无界阻塞队列)
按 FIFO 排序任务,可以设置容量(有界队列),不设置容量则默认使用 Integer.Max_VALUE 作为容量 (无界队列)。该队列的吞吐量高于 ArrayBlockingQueue。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。有两个快捷创建线程池的工厂方法 Executors.newSingleThreadExecutor、Executors.newFixedThreadPool,使用了这个队列,并且都没有设置容量(无界队列)。
3.SynchronousQueue(一个不缓存任务的阻塞队列)
生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
其 吞 吐 量 通 常 高 于LinkedBlockingQueue。 快捷工厂方法 Executors.newCachedThreadPool 所创建的线程池使用此队列。与前面的队列相比,这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
-
PriorityBlockingQueue(具有优先级的无界阻塞队列)
优先级通过参数Comparator实现。 -
DelayQueue(这是一个无界阻塞延迟队列)
底层基于 PriorityBlockingQueue 实现的,队列中每个元素都有过期时间,当从队列获取元素(元素出队)时,只有已经过期的元素才会出队,而队列头部的元素是过期最快的元素。快捷工厂方法 Executors.newScheduledThreadPool 所创建的线程池使用此队列。
Java 中的阻塞队列(BlockingQueue)与普通队列相比,有一个重要的特点:在阻塞队列为空时,会阻塞当前线程的元素获取操作。具体来说,在一个线程从一个空的阻塞队列中取元素时,线程会被阻塞,直到阻塞队列中有了元素;当队列中有元素后,被阻塞的线程会自动被唤醒(唤醒过程不需要用户程序干预)。
六、threadFactory 线程工厂
创建一个线程工厂用来创建线程,可以用来设定线程名、是否为daemon线程等等
七、handler 拒绝策略
AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常。(默认这种)
DiscardPolicy:丢弃任务,但是不抛出异常
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) 。也就是当任务被拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务从队尾添加进去,等待执行。
CallerRunsPolicy:谁调用,谁处理。由调用线程(即提交任务给线程池的线程)处理该任务,如果线程池已经被shutdown则直接丢弃。
一个线程池的使用例子:
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
FutureTask<OssPicResp> ossPicRespFuture = new FutureTask<>(() -> {
RequestContextHolder.setRequestAttributes(servletRequestAttributes, true);//设置子线程共享
return userService.ossUpload(photo, OcrLiveConstant.OSS_ID_CARD_POSITIVE_IMG_SPLICING, super.getMerchantNo(), super.getUserId());
});
FutureTask<TongDunOCRResponseEntity> tongDunOCRResponseEntityFutureTask = new FutureTask<>(() -> {
RequestContextHolder.setRequestAttributes(servletRequestAttributes, true);//设置子线程共享
return userAuthService.ocrAdvance(super.getUserId(), super.getMerchantNo(), photo);
});
GwOssPicResp gwOssPicResp = new GwOssPicResp();
try {
ocrRespTaskExecutor.execute(ossPicRespFuture, 10000);
ocrRespTaskExecutor.execute(tongDunOCRResponseEntityFutureTask, 10000);
//FutureTask的get方式是一个阻塞方法。
gwOssPicResp.setPhotoName(ossPicRespFuture.get().getPhotoName());
gwOssPicResp.setUrl(ossPicRespFuture.get().getUrl());
gwOssPicResp.setTongDunOCRResponseEntity(tongDunOCRResponseEntityFutureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
throw new ServiceException(Messages.getByCode("default.response.errors.system"));
}
return BaseResponse.ok(gwOssPicResp);
}