admin管理员组文章数量:1598089
java线程池配置详解
ThreadPoolExecutor全参构造
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数说明
corePoolSize——核心线程数。
例如设置为5,默认情况下不会初始化线程,
当程序提交任务总数小于6个时,线程池在收到提交的线程时创建线程。
maximumPoolSize——最大线程数。例如设置为10,则线程池内最多创建10个线程存活。
keepAliveTime,unit——线程数多于核心线程数时,线程被回收前的空闲时间(值和单位)。
例如:配置30秒,按以上5,10的示例,当线程池内的线程大于5个时,
有现成空闲时间超过30秒则该线程会被回收。
workQueue——任务队列。当提交的任务需要排队处理时,任务会被放到该队列排队,等待处理。
什么时候入队列?当活跃线程数大于等于核心线程数时,操作入队列。
什么时候创建大于核心线程数的线程?
如果队列满了,调用创建线程方法,创建成功则当前任务开始在新线程中运行。
线程数达到上线了怎么办?执行拒绝策略。
threadFactory——线程工厂。
控制线程池中的线程对象的创建,创建线程时设置线程名,是否后台线程。
handler——拒绝策略。当任务既不能加入到线程执行,又不能加入到任务队列时,按此策略处理
默认采用中断策略,抛出异常。
即提交任务需要拒绝时,抛出RejectedExecutionException异常。
参数作用解释
创建线程池后,提交任务时,当线程池内持有的线程数小于核心线程时,每提交一个任务创建一个线程,直到持有线程数等于核心线程数。
当再次提交任务时,判断有没有空闲线程,有空闲线程则直接执行任务,没有空闲线程(活跃线程数等于核心线程数),则执行入队列操作,将任务添加到任务队列。
添加任务队列时,如果队列未满,则写入队列等待执行。如果队列已满,则执行新建线程流程。
新建线程时,首先判断当前线程数是否达到最大线程数,没有达到则新建线程,并在线程中执行这次操作提交的任务,原来队列中的任务继续排队。如果线程数已达到最大线程数,则执行拒绝策略。
当线程数大于核心线程数时,执行线程回收流程,当线程空闲时间达到配置的保持活跃时间(keepAliveTime,unit)时,该空闲线程被回收。
创建线程的工作由线程工厂完成,创建线程时,设定线程名,是否后台线程,线程优先级。
线程池使用
// 单线程线程池,核心线程和最大线程均为1,任务队列大小为Integer.MAX_VALUE
// 用于处理单线程排队任务
ExecutorService executor = Executors.newSingleThreadExecutor();
// 固定线程数线程池,核心线程和最大线程均为指定的线程数,任务队列大小为Integer.MAX_VALUE
// 用于处理多线程并发处理,还可以指定线程工厂
ExecutorService fixedExecutor = Executors.newFixedThreadPool(5);
// 缓存线程池,核心线程数为0,最大线程数为Integer.MAX_VALUE,空闲时间为1分钟
// 提交任务时,如果有空闲线程则使用空闲线程执行任务,如果没有则创建一个线程执行任务
ExecutorService cachedExecutor = Executors.newCachedThreadPool();
// 计划任务线程池,周期性执行,间隔一定时间触发执行
ExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);
虽然以上三个线程池可以满足大部分业务场景,但里面均没有合理的配置线程池必须的7个参数。实际使用时还要结合自己的场景来选择合适的线程池。
首先需要考虑要解决的问题最多需要多少并发,实际场景有多少并发,自己的服务器又支持多少并发。一般情况下IO类型的可以多一些并发线程,毕竟IO过程消耗网卡资源,对CPU的消耗较小,CPU密集型任务一般不要超过CPU核数。
然后需要选择合适的队列大小,尤其是对任务消耗时间未知,队列排队数量不可预估的情况下,必须制定合适的队列大小,并设置好拒绝策略。任务队列大小不限制的话默认为Integer.MAX_VALUE,这么多的Runnable对象,可能导致内存溢出。
合适的拒绝策略,在任务数超过我们预期的情况时会发挥作用,默认的拒绝策略会抛出异常,指示出线程池有问题了。
线程池的作用
最后简单说一下我理解的线程池的作用。
1. 线程池解决了创建线程消耗资源的问题,主要是通过复用线程整体上减少了资源开销。详情见最后的测试结果(线程与任务耗时测试)。
2. 解决了线程管理的问题,线程作为系统资源,是宝贵而又有限的,程序使用线程又有较高的技术要求,否则可能导致线程资源耗尽,内存溢出,CPU使用率高等问题。
3. 降低了线程使用的门槛,简化了多线程使用方法,通过使用线程池,可以帮助我们更高效的编写简单,优雅,高效的程序。
测试内容
线程与任务耗时测试-个人电脑测试结果:
线程池使用newCachedThreadPool,结果如下:
1万次任务提交,测试10次 | 1万次线程启动测试10次 |
41ms~48ms | 304ms~357ms |
测试代码如下:
// 1万次任务
private static void testTask(){
long t1 = System.nanoTime();
ExecutorService cachedExecutor = Executors.newCachedThreadPool();
for(int i=0;i<10000;i++){
cachedExecutor.submit(new Runnable() {
@Override
public void run() {
}
});
}
cachedExecutor.shutdown();
long t2 = System.nanoTime();
System.out.println("RunTask:"+(t2-t1));
}
//1万次线程
private static void testThread(){
long t1 = System.nanoTime();
for(int i=0;i<10000;i++){
new Thread().start();
}
long t2 = System.nanoTime();
System.out.println("RunThread:"+(t2-t1));
}
版权声明:本文标题:java线程池配置详解 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/xitong/1728298026a1152663.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论