线程池的好处及参数含义
为什么使用线程池
前面的基础中发现创建线程的方式只有一种使用Thread方式,线程启动调用start
方法,运行完销毁,任务少没问题,任务多了就会耗资源
for (int i = 0; i < 10000; i++) {
Thread thread = new Thread(new Task());
thread.start();
}
上面这种方式会占用大量内存,CPU会产生大量的上下文切换,运行完系统回收资源,程序运行效率会降低
问题总结:
- 每一次创建线程和销毁线程都会带来一定的开销,如果任务比较简单,那么就有可能导致创建和销毁线程消耗的资源比线程执行任务本身消耗的资源还要大。
- 每一个线程都需要占用一定的内存空间,不仅如此,线程切换的时候还有上下文切换的开销。
线程池解决问题的思路
第1个问题是体现在创建线程和销毁线程所带来的开销,因此我们最主要的方案就是避免线程的重复创建,那么我们就可以去提前创建好一定数量的线程,然后反复去使用它们。
第2个问题主要是由于线程太多,因此我们的解决思路就是控制线程的总量不至于过多。
使用线程池
使用Executors示例,开发中使用ThreadPoolExecutor自己创建
public class ThreadPool01 {
public static void main(String[] args) {
ExecutorService poolExecutor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 1000; i++) {
poolExecutor.execute(new Task01());
}
}
}
class Task01 implements Runnable {
@Override
public void run() {
System.out.println("thread-name:" + Thread.currentThread().getName());
}
}
执行结果
thread-name:pool-1-thread-1
thread-name:pool-1-thread-2
thread-name:pool-1-thread-3
thread-name:pool-1-thread-4
thread-name:pool-1-thread-4
thread-name:pool-1-thread-2
thread-name:pool-1-thread-1
thread-name:pool-1-thread-1
thread-name:pool-1-thread-4
thread-name:pool-1-thread-1
thread-name:pool-1-thread-3
thread-name:pool-1-thread-5
thread-name:pool-1-thread-5
thread-name:pool-1-thread-5
thread-name:pool-1-thread-5
打印的线程名始终在 Thread Name: pool-1-thread-1~5 之间变化,并没有超过这个范围,也就证明了线程池不会无限制地扩张线程的数量,始终是这5个线程在工作。
线程池的3个好处
- 线程池可以解决线程生命周期的系统开销问题,同时还可以加快响应速度。
- 线程池可以统筹内存和 CPU 的使用,避免资源使用不当
- 线程池可以统一管理资源。比如线程池可以统一管理任务队列和线程,可以统一开始或结束任务,比单个线程逐一处理任务要更方便、更易于管理,同时也有利于数据统计,比如我们可以很方便地统计出已经执行过的任务的数量。
ThreadPoolExecutor
线程池的核心方法是构造方法和execute
方法
- ThreadPoolExecutor(coreSize,MaxSize,keepAlive,
TimeUnit,queue,threadFactory,RejectHandler)
创建线程池并校验参数及初始化参数 - execute(Runnable)
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* 3 步:
*
* 1.如果运行的线程少于核心线程数则开启一个线程,将这个任务作为第一个任务并调用addWorker()方法
* 2. 任务被成功添加到队列,进行双从检查是否要添加一个线程(线程可能检查后终止)或者线程池关闭,因此要重新检查是否要重新入队或者启动新线程
*
* 3. 队列不能添加任务,则新加一个线程,失败了则拒绝任务------
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (work-erCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
worker
private boolean addWorker(Runnable firstTask, boolean core) {
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
if (t.getState() != Thread.State.NEW)
throw new IllegalThreadStateException();
workers.add(w);
workerAdded = true;
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
添加成功则启动一个线程
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker. */
public void run() {
runWorker(this);
}
worker本身就是一个线程
线程池的参数
参数 | 含义 |
---|---|
corePoolSize | 核心线程数 |
maxPoolSize | 最大线程数 |
keepAliveTime+时间单位 | 空闲线程存活时间 |
ThreadFactory | 线程工厂用于创建线程 |
workQueue | 工作队列 |
Handler | 拒绝任务策略 |
线程池的特点
- 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加线程。
- 线程池只有在任务队列填满时才创建多于 corePoolSize 的线程,如果使用的是无界队列(例如 LinkedBlockingQueue),那么由于队列不会满,所以线程数不会超过 corePoolSize。
- 通过设置 corePoolSize 和 maximumPoolSize 为相同的值,就可以创建固定大小的线程池。
- 通过设置 maximumPoolSize 为很高的值,例如 Integer.MAX_VALUE,就可以允许线程池创建任意多的线程。
corePoolSize 与 maximumPoolSize
通过上面的流程图,我们了解了 corePoolSize 和 maximumPoolSize 的具体含义,corePoolSize 指的是核心线程数,线程池初始化时线程数默认为 0,当有新的任务提交后,会创建新线程执行任务,如果不做特殊设置,此后线程数通常不会再小于 corePoolSize ,因为它们是核心线程,即便未来可能没有可执行的任务也不会被销毁。随着任务量的增加,在任务队列满了之后,线程池会进一步创建新线程,最多可以达到 maximumPoolSize 来应对任务多的场景,如果未来线程有空闲,大于 corePoolSize 的线程会被合理回收。所以正常情况下,线程池中的线程数量会处在 corePoolSize 与 maximumPoolSize 的闭区间内。
keepAliveTime+时间单位
当线程池中线程数量多于核心线程数时,而此时又没有任务可做,线程池就会检测线程的 keepAliveTime,如果超过规定的时间,无事可做的线程就会被销毁,以便减少内存的占用和资源消耗。如果后期任务又多了起来,线程池也会根据规则重新创建线程,所以这是一个可伸缩的过程,比较灵活,我们也可以用 setKeepAliveTime 方法动态改变 keepAliveTime 的参数值。
ThreadFactory
ThreadFactory 实际上是一个线程工厂,它的作用是生产线程以便执行任务。我们可以选择使用默认的线程工厂,创建的线程都会在同一个线程组,并拥有一样的优先级,且都不是守护线程,我们也可以选择自己定制线程工厂,以方便给线程自定义命名,不同的线程池内的线程通常会根据具体业务来定制不同的线程名。默认Executors.DefaultThreadFactory
workQueue 和 Handler
workQueue 和 Handler,它们分别对应阻塞队列和任务拒绝策略