线程池作为一种线程资源管理机制,引入的出发点是将任务的执行和提交分离。任务的执行一般不会在当前线程(调用者线程)中进行,而是委托为一个服务,例如线程池。利用Executor框架构造线程池,实现对任务的执行策略的管理,在ExecutorService中提供了对线程池本身的管理。
线程作为一个大对象,构造对象和回收对象都是需要一定资源消耗,线程池可以实现对线程对象的重复利用,以此削减执行任务需要的时间,使用阻塞队列来存储任务相比较于使用线程对象节省了内存。
利用原始的Executor接口,及其实现类ThreadPoolExecutor构造线程池
示例:
<span style="font-family:FangSong_GB2312;font-size:18px;">public class t{
public static void main(String[] args) {
int corePoolSize=1;//核心线程数
int maximumPoolSize=2;//线程池最多运行线程数
long keepAliveTime=1L;//空闲生存时间量值
TimeUnit unit=TimeUnit.SECONDS;//空闲生存时间单位
BlockingQueue<Runnable> workQueue=new LinkedBlockingQueue<Runnable>(10);//任务队列
ThreadFactory threadFactory=new ThreadFactory(){
public Thread newThread(Runnable r){
return new Thread(r);
}
};//线程工厂
RejectedExecutionHandler handler=new ThreadPoolExecutor.AbortPolicy();//拒绝策略
Executor exePool=new ThreadPoolExecutor(corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler);
testRunnable r1=new testRunnable();//任务对象
exePool.execute(r1);
exePool.execute(r1);
}
}
class testRunnable implements Runnable{//测试任务
public void run(){
System.out.println("thread number:"+Thread.currentThread().getId()+" come in");
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("thread number:"+Thread.currentThread().getId()+" come out");
}
}</span>
参数介绍:
<span style="font-family:FangSong_GB2312;font-size:18px;">int corePoolSize 线程池中运行的线程数
int maximumPoolSize 线程池中线程数能达到的最大值
long keepAliveTime 线程数大于corePoolSize值后,线程任务执行完毕被回收之前的等待时间,也叫生存时间
TimeUnit unit 生存时间单位
BlockingQueue<Runnable> workQueue 等待执行的任务队列
ThreadFactory threadFactory 线程工厂,ThreadFactory接口中只有一个newThread方法,后面的实现类会添加线程描述信息
RejectedExecutionHandler handler 拒绝提交的任务时执行的策略,任务队列已满或者线程池已经关闭,
默认的AbortPolicy抛出RejectedExecutionException</span>
在提交任务时,如果线程池中线程数没有达到corePoolSize,则直接创建线程执行,否则将任务存入任务队列,队列存满则判断线程数是否达到maximumPoolSize,没有达到则创建线程(不使用已有的线程),否则执行拒绝策略。
拒绝策略四种类型
AbortPolicy:默认使用的策略,抛出RejectedExecutionException,线程池作为执行线程,在任务执行过程中遇到异常应该交给调用者线程来处理,毕竟自己只是个打工的,出现情况要及时反映给决策者,自己最多做一些清零工作,然后保存异常呈上去。
DiscardPolicy、DiscardOldestPolicy:任务无法提交给服务完成,就直接丢掉了,而且连个招呼都不打,不同在于DiscardPolicy抛弃待提交的任务,DiscardOldestPolicy把等待时间最久的扔掉,并且在这种策略下,Future的get方法(没有等待时间参数的get)也不会抛出异常,因为任务是直接被discard,而不是cancel。
CallerRunsPolicy:队列满了则再提交的任务交由调用者线程来执行,不过此策略下,如果线程池已关闭,则再提交任务不会执行此策略,而且调用者线程被占用,可能使得服务器在应用程序层面一段时间内无法接收新的请求。
ExecutorService、ScheduledExecutorService和Executors
使用原始的Executor和ThreadPoolExecutor构造线程池比较麻烦,在Executors工厂类中提供了直接构造线程池的方法。常用的几种线程池如下:
<span style="font-family:FangSong_GB2312;font-size:18px;">public static ExecutorService newFixedThreadPool(int nThreads)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ExecutorService newCachedThreadPool()
public static ExecutorService newSingleThreadExecutor()</span>
Executors中newFixedThreadPool的实现方法为
<span style="font-family:FangSong_GB2312;font-size:18px;">public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}</span>
ThreadPoolExecutor中的实现为
<span style="font-family:FangSong_GB2312;font-size:18px;">public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}</span>
最终调用的构造方法为
<span style="font-family:FangSong_GB2312;font-size:18px;">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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}</span>
所以包括其他几种类型的线程池工厂方法,都是对七个参数的ThreadPoolExecutor构造方法的包装,以较为简单的方式完成具有不同特点的线程池的构造。
常用线程池类型
newFixedThreadPool、newSingleThreadExecutor:这两种线程池具有固定大小的线程数,使用的都是无界阻塞队列LinkedBlockingQueue,也可以设置大小,虽然存储为任务对象,而非线程对象,但是如果某个时间段存在任务快速到达,并超过线程池处理任务速度,可能存在内存消耗过多问题。其中newSingleThreadExecutor作为单线程线程池,实现了任务的顺序访问,并且保证了任务不会并发的执行,因此程序结构上对线程安全的要求可以放宽,在线程因为异常而取消时,可以自动重新生成一个新的线程。
newCachedThreadPool:具有缓冲功能的线程池,线程数量限制为(0~Integer.MAX_VALUE),即大小不限,生存时间为60秒,保证线程数随实际需求而变动,使用阻塞队列为SynchronousQueue。该队列实现的是一个直接移交任务的功能,在放置任务的时候,必须有取任务的操作与之对应,否则将直接创建线程对象执行任务或执行拒绝策略。
newScheduledThreadPool:常用方法为
<span style="font-family:FangSong_GB2312;font-size:18px;">ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit)
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit)</span>
延迟某个时间后执行和延迟时间后以固定时间间隔执行,提供循环执行功能。ExecutorService提供了对线程池的管理
常用函数有
shutdown函数,不再接受新的线程,等待当前任务以及队列中的任务执行完毕后关闭线程池
shutdownNow函数,不再接受新的线程并且尝试去停止正在执行线程
总结
设计线程池主要关注以下几个方面:
线程池大小,避免两个极端,线程池中线程数量太少时,遇到同时执行的时间较长的任务会导致线程池响应性下降,并且当线程之间存在依赖关系时,可能会发生死锁,导致线程饥饿现象。线程数太多会消耗太多的内存,并且在CPU资源上竞争较严重。
阻塞队列的选择,选择无界的阻塞队列时需要选择合理生存时间,根据任务提交情况,不会导致频繁的线程对象创建和销毁,又不会在线程池中存在过多空闲线程。
因为SynchronousQueue的直接移交任务特性,可以直接交给空闲线程或者创建线程对象执行,所以搭配选择较大线程数量的线程池避免拒绝任务,可以提高执行效率。