在Java中,如果每当一个请求到达就创建一个新线程,开销是相当大的。在实际使用中,每个请求创建新线程的服务器在创建和销毁线程上花费的时间和消耗的系统资源,甚至可能要比花在实际处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个JVM中创建太多的线程,可能会导致系统由于过度消耗内存或者“切换过度”而导致系统资源不足。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务,这就是“池化资源”技术产生的原因。 线程池主要用来解决线程生命周期开销问题和资源不足问题,通过对多个任务重用线程,线程创建的开销被分摊到多个任务上了,而且由于在请求到达时线程已经存在,所以消除了创建所带来的延迟。这样,就可以立即请求服务,使应用程序响应更快。另外,通过适当的调整线程池中的线程数据可以防止出现资源不足的情况。
JDK 1.5以后,Java提供一个线程池ThreadPoolExecutor类。下面从构造函数来分析一下这个线程池的使用方法。 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler); 参数名、说明: * corePoolSize 线程池维护线程的最少数量 * maximumPoolSize 线程池维护线程的最大数量 * keepAliveTime 线程池维护线程所允许的空闲时间 * workQueue 任务队列,用来存放我们所定义的任务处理线程 * threadFactory 线程创建工厂 * handler 线程池对拒绝任务的处理策略 ThreadPoolExecutor将根据corePoolSize和maximumPoolSize设置的边界自动调整池大小。当新任务在方法execute(Runnable) 中提交时, 如果运行的线程少于corePoolSize,则创建新线程来处理请求。 如果正在运行的线程等于corePoolSize时,ThreadPoolExecutor优先往队列中添加任务,直到队列满了,并且没有空闲线程时才创建新的线程。如果设置的corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。 keepAliveTime:当线程数达到maximumPoolSize时,经过某段时间,发现多出的线程出于空闲状态,就进行线程的回收。keepAliveTime就是线程池内最大的空闲时间。 workQueue:当核心线程不能都在处理任务时,新进任务被放在Queue里。
拒绝策略:当任务源源不断的过来,而我们的系统又处理不过来的时候,我们要采取的策略是拒绝服务。RejectedExecutionHandler接口提供了拒绝任务处理的自定义方法的机会。在ThreadPoolExecutor中已经包含四种处理策略。
ThreadPoolExecutor是Executors类的实现,Executors类里面提供了一些静态工厂,生成一些常用的线程池,主要有以下几个: 1. newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 2. newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。(我用的就是这个,同上所述,相当于创建了相同corePoolSize、maximumPoolSize的线程池) 3. newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
测试类:ThreadPool,创建了一个 newFixedThreadPool,最大线程数为3的固定大小线程池。然后模拟10个任务丢进去。主线程结束后会打印一句:主线程结束。
由上可见执行顺序是这样的:线程池创建了三个线程,分别执行任务0、1、2,由于线程创建需要一定时间,因此前三个线程的执行顺序具有一定随机性。此时主线程接着往线程池中塞任务,线程池已达到最大线程数(3),于是开始往队列里放。当某个线程执行完任务后,直接从队列里拉出新的任务执行,队列具有先进先出的特性,因此后面的任务执行是有序的。 这个看一下 Executors 类的源码就更明白了。
实际上是创建了一个具有固定线程数、无界队列的 ThreadPoolExecutor。队列无界,不会拒绝任务提交,因此使用此方法时,需要注意资源被耗尽的情况。 测试类:TestThreadPool,采用有界队列(队列大小2),和默认拒绝策略的ThreadPoolExecutor。
执行结果:
线程池创建了2个线程,分别执行任务0、1,线程池达到corePoolSize,新进任务2、3被放入队列中等待处理,此时队列满,而线程池中线程没有执行完任务0、1,线程池创建新的线程,执行新进任务4、5,达到maximumPoolSize。此时所有任务都没有执行结束,主线程又继续提交任务,线程池进入默认异常策略(AbortPolicy)拒绝服务。