最近看了java编程艺术之后,又看了一边线程池源码,发现自己很多瞎几把用的地方 mark一下
对于线程池的参数理解,和线程池大小的设置
线程池的参数理解
平常工作通常就是
复制
new ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
)
黏贴
new ThreadPoolExecutor(
0,
8,
30,
TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>()
}
0代表核心线程,8代表最大线程数量,30代表超时时间,TimeUnit.MINUTES表示时间单位,new LinkedBlockingQueue() 代表队列
一直这么用也没出问题。
一直到今天发现,没出问题纯粹是运气好
把上面的代码稍稍做出改变
new ThreadPoolExecutor(
0,
8,
30,
TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(10)
}
没错,就是在workQueue当中加一个10,RejectedExecutionException 分分钟出现
为什么那?????
第一个侥幸 在创建工作队列时,使用了无参方法,默认使用了int的最大值创建工作队列,所以这个队列的最大容量可以达到2147483648 21亿基本上的公司都达不到这个量 所以侥幸逃过
那么为什么 队列容量缩小之后就立马出现问题了呐?
这个时候我们就要看到线程池的创建代码
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
在5个参数的构造方法下面,有调用了7参的方法,加入了默认的线程工厂和默认的拦截策略AbortPolicy,
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
java.util.concurrent.ThreadPoolExecutor 类中对拦截策略定义了四种模式
- CallerRunsPolicy :这个策略重试添加当前的任务,他会自动重复调用 execute() 方法,直到成功。
- AbortPolicy :对拒绝任务抛弃处理,并且抛出异常。
- DiscardPolicy :对拒绝任务直接无声抛弃,没有异常信息。
- DiscardOldestPolicy :对拒绝任务不抛弃,而是抛弃队列里面等待最久的一个线程,然后把拒绝任务加到队列。
所以当提交任务数超过maxmumPoolSize+workQueue之和时 也就是示例当中 8+10 超过19个任务的时候,
直接抛出异常 RejectedExecutionException
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
mark 扩展
仔细观察这个类的实现,继承 RejectedExecutionHandler 重写 rejectedExecution,那么是否可以自定义 拦截策略呐?
写一个CustomRejectedExecutionHandler 类 继承 RejectedExecutionHandler 重写 rejectedExecution
private class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
do something
System.out.println("is come");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("error.............");
}
}
}
new ThreadPoolExecutor(
10,
30,
30,
TimeUnit.MINUTES,
new LinkedBlockingQueue<Runnable>(10),
Executors.defaultThreadFactory(),
new CustomRejectedExecutionHandler() );
此时,当我们队列满的时候,就会进入自己的拒绝策略
重写工厂方法也是同理,继承ThreadFactory 重写 newThread方法
线程池的大小设置
通常的百度,都会告诉我们线程池的大小设置分为 io密集型和 cpu密集型,cpu密集型则,线程池大小为 cpu数量, io密集型则为 cpu*2+1
《java开发变成实战》 中给出这样一个公式
Ncpu = CPU的数量
Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1
W/C = 等待时间与计算时间的比率
为保持处理器达到期望的使用率,最优的池的大小等于:
Nthreads = Ncpu x Ucpu x (1 + W/C)
windos环境下 4核心8线程的电脑
写了一段纯cpu代码
long sub=0;
for(long i=0;i<count;i++){
sub+=i;
}
count的值等于 1000000000L 时,单线程计算大约为500毫秒
count的值等于 10000000000L 时,单线程计算大约为2628毫秒
图上只是一小部分数据,实际测试更多次,得出以下结果
实测发现如下特点
1, 8个线程时,单个任务时间基本保持单线程差不多,约等于500毫秒,
100线程时,单个任务的时间上升到2000毫秒左右
说明频繁的上下文切换存在。线程并不是越多越好
2,500ms任务执行数量上升时,100线程反而最快,当任务数量上涨时,优势被正比扩大
,当线程时间大时,存在 线程数量=cpu数量的最优结果
得出结论,公式只是一个参考值,切换和调度并不受控制,实际情况需要根据压测结果设置。特别是当线程的cpu时间较短时。