一 简述
线程池,作为一个管理一组同构工作线程的资源。接受提交的任务,利用线程池中的线程进行工作的处理。 在另一篇《Java多线程设计模式(4)线程池模式》利用非Executors描述了线程池基本构建过程,对于线程池基本机制进行了说明。由于Java类库中有Executor来专门用于线程池的管理的类,所以可以用Executor任务执行框架来实现线程池的构建。
Executor核心的思想就是将请求处理任务的提交线程和任务的实际执行解耦开来。利用execute来传递一个具体执行的Runnable任务类,或者利用submit来传递一个Runnable任务类或Callable获取任务返回值的任务。
对于每次通过execute方法提交的任务执行顺序如下:
1、会判断当前池线程以及核心数目的大小,当池中当前的线程数小于核心线程数时,会创建新的线程。具体创建新线程流程如:获取内置锁,将任务添加到内部的BlockingQueue任务队列中,再利用工厂方法产生一个执行该任务的线程,这个线程是非守护及优先级是NORM的线程。
2、当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3、当线程数大于等于核心线程数,且任务队列已满,采用以下处理方式
若线程数小于最大线程数,创建线程
若线程数等于最大线程数,抛出异常,拒绝任务,进行饱和处理策略。
Executor任务执行框架类图如下:
二 ThreadPoolExecutor线程池
一般线程池可以直接利用构造器来实例化一个ThreadPoolExecutor,也可以利用Executors工具类来创建一个线程池。也可以通过继承扩展ThreadPoolExecutor来自定义一些ThreadPoolExecutor子类。
在利用Executors静态生成ThreadPoolExecutor的时候,都会在内部实例化一个ThreadPoolExecutor。在ThreadPoolExecutor内部都是用BlockingQueue队列来保存提交的任务Runnable。
一个ThreadPoolExecutor需要考虑三个方面:
一个是线程池的大小,二个是任务队列的大小,三个是饱和策略
常见的用法就是利用Executors的静态工厂来创建。
标准的ThreadPoolExecutor构造方法如下:
1
2
3
4
5
6
7
|
public
ThreadPoolExecutor(
int
corePoolSize,
int
maximumPoolSize,
long
keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
|
ThreadPoolExecutor的几个参数说明:
corePoolSize
核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。
核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。
maxPoolSize
当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。
keepAliveTime
当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。
allowCoreThreadTimeout
是否允许核心线程空闲退出,默认值为false。
queueCapacity
任务队列容量。从maxPoolSize的描述上可以看出,任务队列的容量会影响到线程的变化,因此任务队列的长度也需要恰当的设置。
Executors静态工厂创建线程池解释
Executors.newCachedThreadPool();
这种会创建一个目标数目或核心数目为0,最大数目为Integer.MAX_VALUE超时设置1分钟,并且利用SynchronousQueue来存储任务的ThreadPoolExecutor。SynchronousQueue是一种同步移交队列,任务的生产者线程直接将任务移交给任务的消费者线程即工作线程。对于很大的线程池,可以利用SynchronousQueue避免任务的排队。要想将一个元素放入该队列,必须有另一个线程正在等待接收这个元素。当一个新的任务提交后,如果没有线程在等待,且线程池的当前值大小小于最大值,一般ThreadPoolExecutor都会创建一个新的线程。否则就会调用饱和策略来处理。SynchronousQueue这种队列一般用于当线程池无界或者很大的时候采用,目的就是更快的提交任务,充分利用线程池中的工作线程。
Executors.newFixedThreadPool(n);
这种会创建核心数目以及最大数目都是指定初始值的线程池,线程不会超时也就是不会被回收,并且利用无界的(最大值是Integer.MAX_VALUE)的LinkedBlockingQueue来存储任务的ThreadPoolExecutor。
Executors.newSingleThreadExecutor();
这种会创建核心数目以及最大数目都是1的线程池,线程不会超时也就是不会被回收,并且利用无界的(最大值是Integer.MAX_VALUE)的LinkedBlockingQueue来存储任务的ThreadPoolExecutor。
饱和策略解释:
当有界队列被填满后,就需要考虑如何对于再次发送的请求处理。
ThreadPoolExecutor中可以通过setRejectedExecutionHandler设置饱和策略。
ThreadPoolExecutor中包含了四种饱和策略:
AbortPolicy, DiscardPolicy,DiscardOldestPolicy和CallerRunsPolicy
AbortPolicy,即中止策略,是默认的饱和策略,该策略将抛出未检查的RejectedExecutionException。
DiscardPolicy,即抛弃策略,会丢弃队列满后请求的任务。
DiscardOldestPolicy,即抛弃最旧的策略,会抛弃下一个将要被执行的任务,然后尝试重新提交新任务。
CallerRunsPolicy,即调用者策略,既不会抛弃任务,也不会抛出异常,而是将任务回退到调用者。它不会在线程池的某个线程执行新提交的任务,而是在一个调用execute的线程中执行该任务。
注意:
1、可以利用ArrayBlockingQueue,有界的LinkedBlockingQueue、PriorityBlockingQueue来设置存储任务的队列界限。
2、利用Executors静态工厂方法创建ThreadPoolExecutor或者直接实例化ThreadPoolExecutor的对象,默认初始的时候线程并不会立即启动,而是等到有任务提交时候才会启动。当然可以调用prestartAllCoreThreads来启动所有的核心线程。
3、在使用线程池中,当任务是相互独立且类型基本上相同的时候,此时才可以设置线程池和工作队列的界限。
本文转自 zhao_xiao_long 51CTO博客,原文链接:http://blog.51cto.com/computerdragon/1212442