摘要 :
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中
手动声明线程池
《Java阿里巴巴开发手册》中有提到:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。线程池不允许使用 Executors去创建 ,而是通过 去创建,而是通过 ThreadPoolExecutor的方式,避资源耗尽风险。
Executors返回的线程池对象 的弊端 如下 :
FixedThreadPool和 SingleThreadPoolPool : 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
CachedThreadPool和ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM
不建议使用Executors提供的快捷线程池原因:
- 需要根据自己的业务场景,并发情况来评估线程池的几个核心参数,包括核心线程数、最大线程数、线程回收策略、工作队列的类型、拒绝策略,确保线程池的工作符合需求,需要设置有界的工作队列和可控的线程数。
- 自定义线程池指定有意义的名称,以方便排查问题,当线程池出现暴增、线程死锁、线程占用大量cpu、线程执行出现异常问题等,我们往往会抓取线程栈,这个时候有意义的线程名称可以帮助我们快速定位问题。
创建线程池示例:
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.util.NamedThreadFactory;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 线程池添加task时的执行过程:
* 1. 如果池中线程数小于核心线程数,直接创建一个线程入池并执行此task
* 2. 如果池中线程数大于或等于核心线程数,则判断队列中task数量:
* <1>如果队列中task数量未达到队列容量,则task直接进队列(不创建新线程,因为线程是很重的资源,能不新建就不新建,例外情况是
* 如果核心线程数是0,则新建一个线程)
* <2>如果队列中task数量达到队列最大容量,且池中线程数量未达到最大线程数,则创建一个线程入池,并用此线程执行
* 此task(注意,是直接执行本次要添加的task,而不是从队列里拿task)
* <3>如果队列中task数量达到队列最大容量,且池中线程数量达到最大线程数,则根据指定的RejectPolicy来决定:
* CallerRunsPolicy: 用提交task的线程来执行此task,不扔RejectedExecutionException
* AbortPolicy: 直接丢弃此task,扔RejectedExecutionException; 此策略为未指定RejectedExecutionHandler时的默认策略
* DiscardPolicy: 直接丢弃此task,不扔RejectedExecutionException
* DiscardOldestPolicy: 丢弃队列中最老的task,并将此task放入队尾,不扔RejectedExecutionException
* 线程池构造函数:
* int corePoolSize, 核心线程数
* int maximumPoolSize, 最大线程数
* long keepAliveTime, 线程闲置时保持存活的时间数量
* TimeUnit unit, 线程闲置时保持存活的时间单位
* BlockingQueue<Runnable> workQueue, 线程任务队列
* ThreadFactory threadFactory, 线程池用于创建线程的工厂类
* RejectedExecutionHandler handler 线程池满且队列满时的任务提交策略
*
* @author gaohu08299
* @create $ ID: ThreadPools, 2020-10-15 10:51 gaohu08299 Exp $
* @since 1.0.0
*/
@Slf4j
public class ThreadPools {
private ExecutorService logPools;
private ThreadPools(){
logPools = new ThreadPoolExecutor(10,50,15, TimeUnit.MINUTES,new ArrayBlockingQueue<>(1024),new NamedThreadFactory("LOGGER"));
}
public static ThreadPools getInstance(){
return ThreadPoolManagerHolder.instance;
}
private static class ThreadPoolManagerHolder {
public static ThreadPools instance = new ThreadPools();
}
void submitLoggerTask(Runnable task) {
try {
logPools.submit(task);
} catch (Exception e) {
log.error("submitLoggerTask exception{}", e);
}
}
}
使用线程池
public void asyncCacheCityDetailPage(){
ThreadPools.getInstance().submitLoggerTask(()-> submitLogger());
}
线程池默认的工作行为
- 不会初始化corePoolSize 个线程,有任务来了才创建工作线程。
- 当核心线程满了之后不会立即扩容线程池,而是把任务堆积到工作队列中。
- 当工作队列满了之后扩容线程池,一直到线程个数达到maximumPoolSize为止。
- 如果队列已满且达到最大线程后还有任务进来,按照拒绝策略处理。
- 当线程数大于核心线程数时,线程等待keepAliveTime后还是没有任务需要处理的话,收缩线程到核心线程数。