---
title: 线程池
date: 2018-05-07 12:46:51
update: 2018-05-07 12:46:51
categories: THREAD
tags: [thread]
---
### 线程池
#### 线程池的作用
线程池就是处理多线程的形式。
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
#### ThreadPoolExecutor
下面解释下一下构造器中各个参数的含义:
> * corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
1. 核心线程会一直存活,及时没有任务需要执行
2. 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
3. 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
> * maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
1. 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
2. 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
> * keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
> * unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
```
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
```
> * workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
```
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
```
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
> * threadFactory:线程工厂,主要用来创建线程;
> * handler:表示当拒绝处理任务时的策略,有以下四种取值:
```
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
```
#### 使用ThreadPoolExecutor
参数:
corePoolSize:核心处理线程组大小;
maximumPoolSize:线程池容纳最大线程大小;
capacity: 队列任务大小;
```java
package thread;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 3,
200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue(2)); // 队列大小
try {
for (int i = 0; i < 6; i++) {
MyTask myTask = new MyTask(i + 1);
executor.execute(myTask);
System.out.println("线程池中线程数目:" + executor.getPoolSize() + ",队列中等待执行的任务数目:" +
executor.getQueue().size() + ",已执行玩别的任务数目:" + executor.getCompletedTaskCount());
}
} finally {
executor.shutdown();
}
}
}
class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
System.out.println("正在执行task " + taskNum);
try {
Thread.currentThread().sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task " + taskNum + "执行完毕");
}
}
```
上述代码执行流程如下:
> * (corePoolSize = 1, maximumPoolSize = 3, capacity = 1, i = 1)
当池子大小小于corePoolSize时,就新建线程,并处理请求;
> * (corePoolSize = 1, maximumPoolSize = 3, capacity = 1, i = 2)
当池子大小等于corePoolSize时,就新建线程作为新的请求,依次放入workQueue中(先进先出的顺序),
当workQueue满了并且没有新线程时,会使用核心线程组中的空闲线程从workQueue中取请求并处理,如果核心线程组中没有空闲线程,就会等待核心线程组中有空闲线程再从workQueue中取请求并处理;
> * (corePoolSize = 1, maximumPoolSize = 3, capacity = 2, i = 5)
当workQueue满了,有新请求时,就新建线程入池,
当线程池中的线程数不超过maximumPoolSize时这个新线程会立即处理请求,workQueue中的请求等待核心线程组中有空余线程时或有其他空闲线程(其他空闲线程指的是新建的那些线程执行了完任务并且处于空闲状态的那些线程)从队列中依次处理请求;
(corePoolSize = 1, maximumPoolSize = 3, capacity = 2, i = 6)
当前线程池中的线程数超过了maximumPoolSize,创建的新线程会抛出RejectedExecutionException异常,其他线程执行正常,但是代码需要对异常进行处理;
> * 当池子的线程数大于corePoolSize的时候,多余的线程会等待keepAliveTime长的时间,如果无请求可处理就自行销毁
如图:

从中可以发现ThreadPoolExecutor就是依靠BlockingQueue的阻塞机制来维持线程池,当池子里的线程无事可干的时候就通过workQueue.take()阻塞住。
以类比现实生活中看病为例,模拟线程池工作原理,与现实有差异。
executor.execute(myTask):送来一个病人
核心线程组corePoolSize:专家的数量
线程池最大线程数量maximumPoolSize:专家和普通医生总数量
阻塞队列BlockingQueue workQueue:挂完号的病人(有序)
工作流程:
```
当有人来看病时
如果还有专家号就不需要挂号,直接找专家看病
如果专家号满了就需要去挂号等待
有号,等待直到有空闲的专家或者普通医生再去看病
无号,直接找普通医生看病
有普通医生则看病
没有普通医生,那这个病人需要挂下午的号或者医院新增专家或普通医生数量以满足需求。
```
参考: https://www.cnblogs.com/aspirant/p/6920418.html
https://www.cnblogs.com/waytobestcoder/p/5323130.html
https://blog.csdn.net/cutesource/article/details/6061229
线程池