1、为什么要用线程池?
下面先看看不使用线程池的程序,每个任务都新开一个线程处理。
public class NoPool {
public static void main(String[] args) {
// 模拟程序需要执行1000个任务
for (int i = 0; i < 1000; i++) {
// 每个任务启动一条线程去处理
new Thread(new Task(), "Thread" + i).start();
}
}
static class Task implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行任务...");
}
}
}
运行结果:
Thread0执行任务...
Thread3执行任务...
Thread2执行任务...
Thread1执行任务...
Thread5执行任务...
Thread4执行任务...
...
Thread996执行任务...
Thread997执行任务...
Thread998执行任务...
Thread999执行任务...
从执行结果上看,每个任务启动一条线程来执行任务,都顺利完成任务,但是这其中隐藏了一些问题。
- 过多的线程会占用太多的内存
- 每条线程都需要经过创建和销毁这两个生命周期,大量的创建和销毁工作,会降低服务器的工作效率
2、ThreadPoolExecutor 线程池
每个任务创建单独的线程,会带来一些资源和效率方面的问题,这时JDK中的线程池 ThreadPoolExecutor 登场了,它就是来解决和管理线程的相关问题的:
- 用少量的线程来避免内存占用过多问题;
- 让这部分线程都保持工作,且可以反复执行任务——线程可以复用,避免创建和销毁带来的开销,降低资源的消耗,提高响应速度;
- 执行任务的线程总量可控和提高线程的可管理性
ThreadPoolExecutor 的继承关系图:
2.1 构造函数的参数解释
为了让大家能够看懂下面的构造函数源码,这里先给大家介绍构造函数中各个参数的含义。
- corePoolSize :int类型,核心线程数,线程池进行初始化的时候,默认情况下,线程池里没有任何线程,线程池会等待有任务到来的时候,再进行创建新线程去执行任务;
- maxPoolSize :int类型,最大的线程数,线程池有可能会在核心线程数的基础上,额外增加一些线程,但是这些新增加的线程数有一定的上限,就是最大的线程数 maxPoolSize;
- keepAliveTime :long类型,线程空闲时的保持存活时间,如果线程池的当前的线程数多于了corePoolSize,那么超出 corePool 数量的线程,如果其空闲时间超过keepAliveTime,将会被终止;
- workQueue :BlockingQueue类型,工作队列,也就是存储任务的阻塞队列,常见的 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue,我在前面的文章中有介绍过阻塞队列的使用。
- threadFactory :ThreadFactory类型,当线程池需要新的线程的时候,会用ThreadFactory来生成新的线程,默认使用
Exectors.defaultThreadFactory()
,创建出来的线程都在一个线程组,拥有相同的 NORM_PRIORITY 优先级,并且都不是守护线程。如果自己指定 ThreadFactory,那么就可以改变 线程名、线程组、优先级、是否是守护线程等。通常我们使用 Exectors.defaultThreadFactory() 默认的线程工厂就可以了。 - Handler :RejectedExecutionHandler类型,线程池无法接受你所提交的任务的时候,采取的拒绝策略
JDK中提供的4中拒绝策略 RejectedExecutionHandler :
- AbsortPolicy:直接抛出异常,默认策略
- DiscradPolicy:直接丢弃
- DiscardOldestPolicy:丢弃最老的任务
- CallerRunsPolicy:那个线程提交的任务,则由该线程进行运行,这样可以降低提交速度
2.2 线程池的构造函数
从下面的构造函数源码可以看到,所有的构造器都会调用:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
指定 corePoolSize、maximumPoolSize、keepAliveTime 、workQueue、threadFactory 和 RejectedExecutionHandler
- 如果没有指定线程工厂threadFactory,则默认使用Executors.defaultThreadFactory();
- 如果没有指定拒绝策略 RejectedExecutionHandler,则默认使用 AbortPolicy,表示直接抛出异常。
构造函数源码:
// 线程池无法接受你所提交的任务的时候,默认采取的拒绝策略:AbortPolicy 直接抛出异常
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
// 指定 corePoolSize、maximumPoolSize、keepAliveTime 和 workQueue
// 使用默认的 threadFactory:Executors.defaultThreadFactory() 和 默认的RejectedExecutionHandler:AbortPolicy
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
}
// 指定 corePoolSize、maximumPoolSize、keepAliveTime 、workQueue 和 threadFactory
// 使用默认的RejectedExecutionHandler:AbortPolicy
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);
}
// 指定 corePoolSize、maximumPoolSize、keepAliveTime 、workQueue 和 RejectedExecutionHandler
// 使用默认的 threadFactory:Executors.defaultThreadFactory()
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler);
}
// 指定 corePoolSize、maximumPoolSize、keepAliveTime 、workQueue、threadFactory 和 RejectedExecutionHandler
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
// 检验参数的合法性
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
// 初始化相关的成员变量
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
2.3 ThreadPoolExecutor 添加执行任务(Runnable)的步骤
-
- 如果线程小于corePoolSize的时候,即使线程有处于空闲状态,也会继续创建新的线程运行新的任务
-
- 如果等于大于corePoolSize,但是小于maxPoolSize,则将任务放入工作队列(阻塞队列),从队列中取任务时,如果当前有效的线程数目小于corePoolSize,使用 take() 方法获取任务,否则使用 poll(keepAliveTime, TimeUnit.NANOSECONDS) 获取任务;
-
- 如果队列已满,并且线程数小于maxPoolSize,创建新的线程来运行人任务。
-
- 如果队列已满,并且线程数大于或者等于maxPoolSize,则拒绝该任务,拒绝策略由
RejectedExecutionHandler
决定。
- 如果队列已满,并且线程数大于或者等于maxPoolSize,则拒绝该任务,拒绝策略由
3、Executors 创建线程池实例
Executors 提供了大量静态工厂方法,用于创建线程池实例,下面讲解几个常用的静态工厂方法。
3.1、static ExecutorService newFixedThreadPool(int nThreads)
Executors 中 newFixedThreadPool 的源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newFixedThreadPool(int nThreads) :用于创建固定线程数的线程池。
- corePoolSize 和 maximumPoolSize 一样,都等于入参 nThreads;
- keepAliveTime 等于0,因为线程数永远不会大于 corePoolSize,所以这里keepAliveTime这个参数没有作用,设置为0;
- workQueue 是 LinkedBlockingQueue 实例,通过 new LinkedBlockingQueue<Runnable>() 空参构造器创建实例,没有指定阻塞队列的容量,则队列的默认容量是 Integer.MAX_VALUE;
注意: 由于传进去的 LinkBlockingQueue 的容量是 Integer.MAX_VALUE,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致 OOM。
3.2、static ExecutorService newSingleThreadExecutor()
Executors 中 newSingleThreadExecutor 的源码
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newSingleThreadExecutor() :用于创建只有一条线程的线程池。
通过源码,可以看出,newSingleThreadExecutor() 和 newFixedThreadPool(int nThreads) 基本一样,不同的是在 newSingleThreadExecutor() 中 corePoolSize 和 maximumPoolSize 都固定等于 1 ,其它参数跟newFixedThreadPool(int nThreads) 一样;
注意: newSingleThreadExecutor() 也可能会导致跟 newFixedThreadPool(int nThreads) 同样的问题,也就是 LinkBlockingQueue 的容量是 Integer.MAX_VALUE,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致 OOM。
3.3、static ExecutorService newCachedThreadPool()
Executors 中 newCachedThreadPool 的源码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newCachedThreadPool() :创建无界线程池,maximumPoolSize = Integer.MAX_VALUE 近似无界,,具有自动回收多余线程的功能。
- corePoolSize = 0 说明核心线程数为0,
- maximumPoolSize = Integer.MAX_VALUE,最大可创建 Integer.MAX_VALUE 个线程;
- keepAliveTime 等于60s,因为核心线程数 corePoolSize 为0,所以只要线程池中的线程空闲超过 keepAliveTime 都会被自动回收;
- workQueue 是 SynchronousQueue 实例,这是一个不存储元素的阻塞队列,它的容量为 0,每一个put操作都要等待一个take操作,也就是说如果当前线程池中所有线程都正在忙,则为该任务创建一个新线程并将这个放入池中。
注意: 第二个参数 maxPoolSize 被设置为了 Integer.MAX_VALUE,这可能会创建数量非常多的线程,甚至导致 OOM。
3.4、static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
Executors 中 newScheduledThreadPool 的源码
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
**ScheduledThreadPoolExecutor 构造器,指定核心线程数 corePoolSize **
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService {
...
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
...
}
newScheduledThreadPool(int corePoolSize) :创建支持延迟任务、周期性执行任务的线程池。
- corePoolSize 通过入参 corePoolSize 指定;
- maximumPoolSize = Integer.MAX_VALUE,最大可创建 Integer.MAX_VALUE 个线程;
- keepAliveTime 等于0,如果线程池的当前的线程数多于了corePoolSize,那么超出 corePool 数量的线程一旦空闲,就被回收
- workQueue 是 DelayedWorkQueue 实例,这个队列是无界队列,支持延时获取的元素的阻塞队列
3.4.1 ScheduledExecutorService 方法说明:
-
schedule(Runnable command, long delay, TimeUnit unit) 和 schedule(Callable callable, long delay, TimeUnit unit) :
指定延迟时间,延时执行任务,只执行一次 -
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) :
周期性任务,提交固定时间间隔的任务,前后两个任务的起始时间间隔是固定的 -
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) :
周期性任务,提交固定延时间隔执行的任务,前一个任务结束时间到后一个任务开始时间的时间间隔是固定的
3.5 Executors 静态方法参数对比
Parameter | FixedThreadPool | CachedThreadPool | ScheduledThreadPool | SingleThreadPoolExecutor |
---|---|---|---|---|
corePoolSize | 入参 nThreads | 0 | 入参 corePoolSize | 1 |
maxPoolSize | 入参 nThreads | Integer.MAX_VALUE | 入参 corePoolSize | 1 |
keepAliveTime | 0 | 60 seconds | 0 | 0 |
3.6 线程池手动创建
还是自动创建
建议:手动创建更好,因为这样可以让我们更加明确线程池的运行规则,避免资源耗尽 和 OOM(内存溢出) 的风险
4、线程池中的线程数量设定为多少比较合适?
-
计算密集型的
CPU密集型(加密、计算hash等):最佳线程数为CPU核心数的 1-2 倍左右。 -
耗时IO型的
耗时IO型(读写数据库、文件、网络读写等):最佳线程数一般会大于cpu核心数很多倍,以jvm线程监控显示繁忙情况为依据。保证线程空闲可以衔接上。
Brain Goetz 推荐的计算方法
参考 Brain Goetz 推荐的计算方法:线程数:cpu核心数 * (1+平均等待时间/平均工作时间)
这篇文章仅仅是介绍了ThreadPoolExecutor的构造方法,如何创建一个线程池,详细地说明了ThreadPoolExecutor的构造方法中各个参数的含义。接着又讲解了Executors 几个常用的静态工厂方法,用于创建线程池实例,朋友们可以更加业务需求去选择,但是我建议最好是通过 ThreadPoolExecutor 的构造函数去创建线程池,这样资源消耗可控性更强。
下篇文章介绍 ThreadPoolExecutor 和 Executors 创建的线程池的使用案例,以及ThreadPoolExecutor 中各个核心函数用法。
代码:
github.com/wengxingxia/002juc.git