## **什么是线程池?**
线程池可以理解为执行特点数量线程的管理容器
## **线程池作用**
* **降低资源消耗**
通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
* **提高响应速度**
当任务到达时,任务可以不需要等到线程创建就能立即执行。
* **提高线程的可管理性**
使用线程池可以进行统一分配、调优和监控
## **线程池的创建方式**
Java通过Executors(jdk1.5并发包)提供四种线程池
* newCachedThreadPool
创建一个相对无限大可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
```
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int temp = i;
newCachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ",i:" + temp);
}
});
}
```
源码分析
```
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
```
newCachedThreadPool方法只是对ThreadPoolExecutor方法做了简单的封装,其中
1.corePoolSize:核心线程数为0
2.maximumPoolSize:最大线程数为最大整数值Integer.MAX\_VALUE,可以理解为无限大
3.keepAliveTime:空闲等待销毁时间为60S
4.workQueue:线程缓存队列采用的是SynchronousQueue单元素阻塞队列
总结:核心线程数小于最大线程数且为0,所以任务完成后60S所有线程都会销毁,因为采用的是SynchronousQueue单元素阻塞队列,队列无法缓存多个任务,所以多任务时会不停的创建线程。
* newFixedThreadPool
创建一个固定长度的线程池,可控制线程最大并发数,超出的线程会在队列中等待
```
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int temp = i;
newFixedThreadPool.execute(new Runnable() {
@Override
public void run() {
...
Thread.sleep(100);
...
System.out.println(Thread.currentThread().getName() + ",i:" + temp);
}
});
}
```
执行结果如下、并且程序不会退出
```
pool-1-thread-1,i:0
pool-1-thread-2,i:1
pool-1-thread-3,i:2
pool-1-thread-1,i:3
pool-1-thread-2,i:4
pool-1-thread-3,i:5
pool-1-thread-1,i:6
pool-1-thread-2,i:7
pool-1-thread-3,i:8
pool-1-thread-1,i:9
```
源码分析
```
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
```
newFixedThreadPool方法也只是对ThreadPoolExecutor方法做了简单的封装,其中
1.corePoolSize:核心线程数为用户设定的值
2.maximumPoolSize:最大线程数为等于核心线程数
3.keepAliveTime:空闲等待销毁时间为0S
4.workQueue:线程缓存队列采用的是LinkedBlockingQueue队列
总结:核心线程数等于最大线程数、所以线程池在创建三个线程后会一直维持,等待的任务保存在LinkedBlockingQueue队列中,任务完成后线程池维持3个线程等待任务,剩余其他线程销毁。
* newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行
```
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(3);
for (int i = 0; i < 10; i++) {
final int temp = i;
newScheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
...
Thread.sleep(100);
...
System.out.println(Thread.currentThread().getName() + ",i:" + temp);
}
}, 3, TimeUnit.SECONDS);
}
```
表示延迟3秒处理
源码分析
```
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService {
...
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
...
}
```
ScheduledThreadPoolExecutor也是继承ThreadPoolExecutor了方法,其中
1.corePoolSize:核心线程数为用户设定的值
2.maximumPoolSize:最大线程数为最大整数值Integer.MAX\_VALUE,可以理解为无限大
3.keepAliveTime:空闲等待销毁时间为0S
4.workQueue:线程缓存队列采用的是专门的DelayedWorkQueue队列
总结:线程可无限创建,但是当任务数量小于核心线程数时,多余的线程都会第一时间被销毁,只保留核心线程数量值的线程。
* newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
```
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int temp = i;
newSingleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ",i:" + temp);
}
});
}
```
源码分析
```
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
```
底层使用的还是ThreadPoolExecutor的构造方法,其中
1.corePoolSize:核心线程数为1
2.maximumPoolSize:最大线程数为1
3.keepAliveTime:空闲等待销毁时间为0S
4.workQueue:缓存队列采用的是LinkedBlockingQueue队列
总结:只创建并且保持一个线程在执行任务,所有等待任务保存在LinkedBlockingQueue队列中
总结上面4中创建线程池的方法,似乎发现最终执行的还是ThreadPoolExecutor的构造方法,只是构造参数不同而已
* corePoolSize
核心池的大小。 当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
* maximumPoolSize
线程池最大线程数,它表示在线程池中最多能创建多少个线程
* keepAliveTime
最大连接数大于核心线程数时,空闲等待的线程待销毁的时间
* workQueue
缓存队列,线程池中线程数量大于核心线程数时,任务存入次队列中
## **线程池原理分析**
线程池的处理流程
![](https://img.kancloud.cn/79/86/79868da81af0c67d5a4347d933c14352_776x452.jpg)
## **自定义线程池**
创建线程任务
```
class TaskThread implements Runnable {
private String name;
public TaskThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + name);
}
}
```
自定义线程池
```
class test {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2,
60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
for (int i = 0; i < 10; i++) {
executor.execute(new TaskThread("任务" + i));
}
executor.shutdown();
}
}
```
自定义线程中
1.corePoolSize:核心线程数为1
2.maximumPoolSize:最大线程数为2
3.keepAliveTime:空闲等待销毁时间为60S
4.workQueue:缓存队列采用的是ArrayBlockingQueue长度为5的有界队列
执行结果
```
java.util.concurrent.RejectedExecutionException...
pool-1-thread-2任务6
pool-1-thread-2任务1
pool-1-thread-2任务2
pool-1-thread-2任务3
pool-1-thread-2任务4
pool-1-thread-2任务5
pool-1-thread-1任务0
```
最终执行了7次的任务,最后抛出RejectedExecutionException异常警告,那就来分析一下原因吧。
根据线程池的执行流程可分析
1.核心线程数为1,线程池会创建一个线程执行一个任务任务
2.当任务数量大于核心线程数量时,线程池会将未被分配执行的任务保存待缓存队列中,因为缓存队列是初始化长度为5的有界队列,所有只能缓存5个任务。
3.缓存队列存储满了之后,线程池判断最大线程数,发现最大线程数为2,因为当前只创建了1个线程,所有线程池会再去创建一个线程去执行一个任务。
4.到最后线程池发现最大的线程数已经满了,但是还是有任务需要执行,于似乎直接拒绝处理抛出异常。
## **合理配置线程池**
需要根据任务的特性来分析
1. 任务的性质:CPU密集型、IO密集型和混杂型
2. 任务的优先级:高中低
3. 任务执行的时间:长中短
4. 任务的依赖性:是否依赖数据库或者其他系统资源
* CPU密集型任务应配置尽可能小的线程,如配置CPU个数+1的线程数
* IO密集型任务应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,如配置两倍CPU个数+1
* 混合型的任务,如果可以拆分,拆分成IO密集型和CPU密集型分别处理,前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了
* 前人总结的 最佳线程数=((线程等待时间+线程CPU时间)/线程CPU时间 )\* CPU数目