1. 什么是线程池?
- 线程的创建和销毁是有一定的开销的,为了减少开销(内存和垃圾回收),我们通过创建线程池来执行大量的任务,避免反复创建并销毁线程所带来的问题,如果不用线程池,一旦任务数量过多,新建线程过多可能会OOM异常,即内存溢出。
- 好处:
- 限制线程资源的总量,复用每一个线程,同一管理资源
- 加快响应速度,合理利用CPU和内存
- 适用场景:
- 服务器接收大量请求(Tomcat本身就是用线程池来实现的)
- 开发中,如果需要创建5个以上的线程,就可以用线程池
2. 创建和停止线程池?
2.1. 线程池构造函数的参数
参数 | 含义 |
---|---|
corePoolSize | 核心线程数 |
maxPoolSize | 最大线程数 |
keepAliveTime | 保持存活时间 |
workQueue | 任务存储队列 |
threadFactory | 当线程池需要新的线程的时候,会使用threadFactory来生成新的线程 |
Handler | 由于线程池无法接受你所提交的任务的拒绝策略 |
-
corePoolSize :线程完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时,再创建新线程去执行任务;
-
maxPoolSize:线程池有可能会在核心线程数的基础上,额外增加一些线程,但是这些新增加的线程数有一个上线,就是最大线程数;
什么情况下扩展到maxPoolSize?
1. 如果线程数小于corePoolSize ,即使其他工作线程处于空闲状态,也会创建一个新线程来运行新任务; 2. 如果线程数等于或大于corePoolSize 但是少于maxPoolSize,则将任务放入workQueue队列(指定队列的容量); 3. 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程来运行任务; 4. 如果队列已满,并在线程数大于maxPoolSize,则执行Handler拒绝策略;
总之,是否需要增加线程的判断顺序是:
corePoolSize -》workQueue-》maxPoolSize
增减线程的特点:
1. 通过corePoolSize 和maxPoolSize相同,就可以创建固定大小的线程池; 2. 线程池希望保持较少的线程数,并且只有在负载变得很大的时候才增加它; 3. 通过设置maxPoolSize为很高的值,可以允许线程池容纳任意数量的并发任务; 4. 只有在队列填满时才多于corePoolSize 的线程,所以如果你使用的是无界队列,那么线程数就不会超过corePoolSize ;
-
keepAliveTime:如果线程池当前的线程数多于corePoolSize ,那么如果多余的线程空闲时间超过keepAliveTime,它们就会被终止,比如:corePoolSize 为5,当前线程数为10,如果线程空闲了,就会恢复到5;
-
ThreadFactory:用来创建线程,默认是非守护线程,优先级是5,一共有10个等级;
-
workQueue:有3种最常见的队列类型:
1. 直接交接:SynchronousQueue,本身没有容量,maxpoolSize要设置大一些; 2. 无界队列:LinkedBlockingQueue,maxpoolSize设置多大都没有用,会一直缓存新增的任务,有可能造成OOM; 3. 有界队列:ArrayBlockingQueue,队列容量指定,满了之后,就创建新的线程;
2.2 线程池应该手动创建还是自动创建
Excutors类创建线程池的方法如下:
1. newFixedThreadPool:由于传进去的LinkedBlockingQueue是没有容量上限的,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成OOM。
2. newSingleThreadExecutor:和newFixedThreadPool的原理基本一样,都采用LinkedBlockingQueue无界队列,只不过把corePoolSize 和maxPoolSize都改成了1,请求堆积的时候也会占用大量的内存。
3. newCachedThreadPool:可缓存线程池,使用SynchronousQueue直接队列,corePoolSize 为0,maxPoolSize设置为Integer.Max_Value,线程可以无限创建,但是请求过多且无法及时处理完毕,也会导致OOM。
4. newScheduledThreadPool:支持定时以及周期性任务执行的线程池,采用DelayedWorkQueue延迟队列,可以指定延迟时间执行任务,还可以以一定时间间隔来运行。
正确创建线程池的方法是手动创建:根据不同的业务场景,自己设置线程池参数,比如内存大小,线程名字等等。
2.3 线程池里的线程数量设定为多少比较合适?
- CPU密集型:比如八核处理器,那可以把corePoolSize 设置为核心数的1-2倍;
- 耗时IO型(读写文件):这种类型的CPU是不工作的,可以设置为核心数的很多倍
- 线程数=CPU核心数*(1+平均等待时间/平均工作时间)
2.4 停止线程池的正确方法
- shutdown,初始化整个关闭过程,不是直接关闭,把正在执行以及队列中等待的任务执行完毕再关闭,然后拒绝新的任务;
public class StopThreadPool {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 50; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println("执行已经提交的线程:" +Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
Thread.sleep(1000);
executorService.shutdown();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("继续提交线程池");
}
});
}
}
执行已经提交的线程:pool-1-thread