重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
多线程一直是Java进阶的必修课。在Java中,我们很早就知道可以通过 Thread 类和 Runnable 接口来实现多线程。与之有着类似职责的数据库连接,也可通过JDBC创建与使用。但我们深知无论是数据库连接的创建与销毁,还是线程的创建与销毁,都是一件及其消耗性能的事情。为了减少这种情况的发生,前辈们就在思考,是不是可以复用已有的数据库连接?减少创建,销毁动作?这就是后来数据库连接池的由来。同样的,为了复用线程,也就有了线程池。我一直独自暗喜,身为一位幸福的Java程序员,前有Java虚拟机管理内存,后有Doug Lea 大师提供并发库,锁机制。简直幸福的不像话,不过幸福归幸福,该掌握的还是需要掌握的,我们一起来看看今天的主角:ThreadPoolExecutor。
专注于为中小企业提供成都网站制作、网站建设服务,电脑端+手机端+微信端的三站合一,更高效的管理,为中小企业太原免费做网站提供优质的服务。我们立足成都,凝聚了一批互联网行业人才,有力地推动了上1000+企业的稳健成长,帮助中小企业通过网站建设实现规模扩充和转变。
在面试过程中,也时常会遇到一些关于线程池的问题,例如:
线程池中核心参数有哪些?
有没有自己实现过线程池?
如果让你自己实现线程池,你会怎么做?
…
这些问题,其实考核的就是Java线程池的知识,更具体一点就是对ThreadPoolExector类熟不熟悉,下面代码是ThreadPoolExector类的全参构造函数。下面我们就一一对参数进行了解。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue 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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
其中:
corePoolSize:表示该线程池最小的工作线程数。默认情况下,当需要使用时创建线程,也可以调用 prestartAllCoreThreads() 方法进行预创建所有的核心线程。
maximumPoolSize:表示该线程池最大的线程数量,理论上将其设置为无限大,就会创建无限多的线程,当然,创建线程的数量最终由系统资源也就是操作系统决定。
keepAliveTime:表示空闲线程的超时时间,(单位为纳秒)。但在构造函数中,单位与unit 参数配合使用,最终转换为纳秒。
unit:表示空闲线程超时的时间单位,可选值有:java.util.concurrent.TimeUnit中的值,SECONDS(秒),MINUTES(分),HOURS(小时),DAYS(天) 等。
workQueue :表示工作队列(其实是一个runnable队列,在线程池中定义为Worker),其基类为:java.util.concurrent.BlockingQueue。
threadFactory:线程工厂,通常用于创建线程,以及命令规则。默认为: Executors.defaultThreadFactory()。
handler 表示处理策略,当workQueue队列满时,以及创建线程错误时的处理策略。其基类为 java.util.concurrent.RejectedExecutionHandler。默认为:AbortPolicy 策略。
在 java.util.concurrent.Executors类为我们提供了多种组合,其底层还是调用ThreadPoolExecutor。下面列举几个常用的方法:
1. newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
...
特性:线程数量大小固定,且 corePoolSize 与 maximumPoolSize 数量相等。当线程数量设置太少时。task则会积压在LinkedBlockingQueue队列中。当 task 任务大于Integer.MAX_VALUE时 则会有OOM发生的风与之类是的还有newSingleThreadExecutor方法。
2. newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
特性:corePoolSize数量为0,maximumPoolSize数量为Integer.MAX_VALUE,也就是说理论上是可以创建Integer.MAX_VALUE个线程的。keepAiveTime时间为 60秒。BlockingQueue使用的是SynchronousQueue,由于其没用容量,意味这每一次put对应着一次take操作,其吞吐量比较高。正因为如此,当task到达一定程度时,可能会创建许多线程,从而导致OOM,甚至服务不可用。
上述方法其实是对ThreadPoolExecutor方法的封装对不对,知道了ThreadPoolExecutor的每一个参数,再来使用这个,就得心应手了对不对。
Executors类这么方便,是不是可以直接使用Executors类来创建呢?当然可以,但并不建议这样做。在《阿里Java手册》中的并发处理小节中有提到:
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式可以让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool :
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM 。
2) CachedThreadPool 和 ScheduledThreadPool :允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。
在实际应用中,我们应该遵守规范,避免掉一些没必要的问题。该规约其最终目的是让大家能够更清楚了解线程池的每个参数,从而达到能够在实际应用场景中调为最优组合使用,使其达到最大性能。同样的,我们也通过安装阿里巴巴的规约插件进行自动扫描与提醒。在Idea中 File -> Setings -> Plugins -> Browse repositories中搜索『Alibaba Java Coding Guidelines』安装即可!
在这篇文章中,算是对ThreadPoolExector的一个初步了解。知道了其核心参数,到底是怎么回事。但这还并不够,且不足以学以致用,还有很多疑问,如:
ThreadPoolExecutor的原理是怎样的?
我们如何自定义一个线程池?
ThreadPoolExecutor在Dubbo中的实践
…
这些疑惑都需要一一去揭晓。由于篇幅原因,这些会作为好几篇文章进行记录。其目的是能够学以致用,面试时也能得心应手。
相关阅读:
《使用 Mybatis 真心不要偷懒!》
《再谈Java 生产神器 BTrace》
《Java 生产神器 BTrace》
《重构不完全指南!》
如果想深入学习Java并发编程,下面这本书是值得阅读的。当然了,如果你读喜欢电子书,也可以回复公众号消息『Java并发编程的艺术』进行免费获取!