线程池就是提前创建若干个线程,若有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。减少频繁创建和销毁线程消耗系统资源。
频繁创建、销毁 线程。会对系统资源的极大浪费。如果无限制地创建,不仅会消耗系统资源,还会降低系统稳定性。因此,实际开发会使用线程池来管理、复用线程。
Java从1.5 Executors 类提供四种创建线程池方式
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),threadFactory);
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
主要原因:队列堆积,有OOM风险
允许的请求队列(底层实现是LinkedBlockingQueue)队列长度为Integer.MAX_VALUE,
可能会堆积大量的请求,从而导致OOM
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
参数 | 含义 |
---|---|
corePoolSize | 核心线程数量,一直存在 除非allowCoreThreadTimeOut设置为true |
maximumPoolSize | 线程池允许的最大线程池数量 |
keepAliveTime | 线程数量超过corePoolSize, 空闲线程的最大超时时间 |
unit | 超时时间的单位 |
workQueue | 工作队列,保存等待执行任务的阻塞队列 |
threadFactory | 自定义线程的工厂类,一般用来设置线程名称 |
handler | 当线程池和队列都满了,再加入线程会执行此策略 详见第8点 |
附1.时间参数取值范围
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
附2.常见阻塞队列
综上,一般建议使用LinkedBlockingQueue,并合理设置队列大小
拒绝策略也可以叫饱和策略,作为线程池定义的最后一个参数,同样至关重要。线程数和队列不可能无穷大,当队列和线程都满了的时候,那么必须采取一种策略去处理继续追加的任务。于是就有了拒绝策略的引入。
以下是内置的4中拒绝策略,默认是ThreadPoolExecutor.AbortPolicy,当超过最大值时直接抛出异常
ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常 |
---|---|
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常 |
ThreadPoolExecutor.DiscardOldestPolicy | 丢弃队列最前面的任务,然后重新提交被拒绝的任务 |
ThreadPoolExecutor.CallerRunsPolicy | 由调用线程(提交任务的线程)处理该任务 |
《Java并发编程实战》中最原始的公式是这样的:
N = N * U * (1 + W/C)
其中 _N_是_CPU_核心数 , _U_是_CPU_使用率介于0~1之间 , W/C 是等待时间与计算时间的比率
java可以通过如下代码获取当前设备的CPU个数。
Runtime.getRuntime().availableProcessors()
实际使用中一般会根据任务类型划分,对于不同类型的任务适当分配不同大小的线程池
结论:线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程
ThreadPoolExecutor提供了两个方法,用于线程池的关闭
关注程序员小强公众号更多编程趣事,知识心得与您分享