文章内容包括:线程池的创建方式、参数、拒绝策略、如何合理配置线程池。
目录
使用Executors创建线程池
这里用Executors类的静态方法创建了固定数量的线程池,也可以去创建带缓存的线程池、单一线程池
在使用Executors创建不同线程池的时关键参数意义
都在Executors的源码里可见:
corePoolSize 线程池中必须至少活跃core个线程,尽管他们是没有任务做
maximumPoolSize 线程池最大容量
keepAliveTime 线程池中活跃的线程大于core个时,这些线程最多等待KeepAliveTime之后仍没有任务,就可以下班休息了
workQueue 用于保存没有待执行任务
unit keepAliveTime的单位
threadFactory 用于创建线程
handler 当队列排满了且线程池容量已经达到组最大力,如何拒绝后续请求
Executors创建线程池的弊端
原因:进入Executors的源码可以看到:
Executors 的LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择进行设置,默认最大长度为Integer.MAX_VALUE,而上面用newFixedThreadPool中创建线程池时,LinkedBlockingQueue并未指定容量。此时,LinkedBlockingQueue就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致OOM问题。
所以我们在实际生产中,固定线程池、单一线程池、都不用,而是直接使用ThreadPoolExecutor的构造函数来自定义创建线程池,规避这些风险
使用ThreadPoolExecutor创建线程池
public static void main(String[] args) {
// 直接调用ThreadPoolExecutor的构造函数,指定参数来创建线程池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, 5, 2, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2));
// 提交7个任务,恰好等于恰好等于maximumPoolSize[最大线程数] + capacity[队列大小]
for (int i = 1; i <= 7; i++) {
final int idx = i;
// ()->{}等于 new Runnable(){}
poolExecutor.submit(
()->{
System.out.println(Thread.currentThread().getName()+"处理任务"+idx);
try {
// 模拟线程执行时间,1s
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
分析执行过程:
最大容量:5,work队列是2,所以该线程池最多可接纳7个任务,
这里用for循环提交7个任务,假设这里是大于7个任务,那么后面牵扯到对于多余的任务如何处理,就需要使用handler
提交任务1,直接创建Thread1来服务
提交任务2,直接创建Thread2来服务
因为coreThread是2个
提交任务3,到work队列排队
提交任务4,到work队列排队
此时coreThread都在工作,work队列也排满了
提交任务5,创建Thread5服务
提交任务6,创建Thread6服务
提交任务7,创建Thread7服务
此时已经达到线程池最大容量,所以对于work队列中排队的任务,需要等core和其他工作结束后,再服务
先拿任务3服务,再拿任务4服务
线程池拒绝策略
看下面这个例子:
当我们没有指定拒绝策略的时候,默认是AbortPolicy,对于额外的任务直接抛出异常
当我们使用CallerRunsPolicy策略时,对于额外的任务,不会丢弃不会抛异常,而是回调给调用者处理该任务
当我们使用DiscardOldestPolicy策略时,会丢弃队列中等待时间最长的任务,然后执行额外的任务
当我们使用Discard策略时,对多出的任务直接丢弃,不报异常
如何合理配置线程池容量?
计算密集型
计算密集,我们肯定是要把CPU充分利用起来,所以对于Ncpu的系统,通常创建Ncpu+1个线程的线程池来获得最优利用率
(加入计算密集型线程恰好在某一页发生错误而暂定,我们就还有一个额外的线程来处理,可以确保这种情况下cpu不会中断工作)
IO密集型
举个例子:文件的上传下载就是IO密集型,阻塞耗时比计算多的多。IO型的一般都会对网络带宽要求比CPU处理要求高。
我们需要考虑系统内存、需要考虑在服务器上创建几个线程比较合适
生产中一般会有几百万几万的高并发,其实后台都是这些线程在轮询的,轮到谁执行谁。比如百度网盘或者迅雷下载文件的时候
在迅雷下载电影的时候可以选择线程数,一个文件原来只有一个线程下载,下载分5个线程下载,当然会快,但他到最后99%的时候可能会卡一下,这是因为他需要把5个线程下载的文件做合并。
高并发下怎么选择最优线程数
实际应用中,如何设置并发线程的数量?
假设一个系统的TPS(TransactionPerSecond每秒处理事务量)是20,一个Transaction由一个线程完成,每个线程处理一个Transaction的时间是4s,
设计多少个线程,使得1s内处理完20个Transaction??
解:一个线程1s内处理1/4=0.25个事务,20个事务1s内完成需要:20/0.25=80个线程
动态调整
线程演变为协程
动态调整
不同情况不同调整
1、任务少,活跃线程数少,可以适当降低线程数量,节省系统资源
2、若队列内排队任务非常多,甚至满了
若系统负载不高,可以考虑渐进式增加线程数量来处理任务、
若系统负载尚可,可以考虑水平扩展,增加机器数量
若系统负载高,降低每个机器的线程数量,增加机器的数量
3、若存在脉冲流量
若系统内存充足,可以考虑扩大等待队列
若系统内容不足,可考虑增加机器节点