Java中的线程池是日常工作中用的比较频繁的并发框架了,几乎所有需要并发执行的程序都可以使用线程池。那么,使用线程池的程序究竟有什么好处呢?答案无非就是以下两点。
- 降低资源消耗。
通过重复利用已创建的线程,降低了频繁切换线程上下文(创建与销毁)所带来的资源消耗。 - 提高了线程的可管理性。
线程是稀缺资源,如果无限制的创建,过多的消耗系统资源,可能会导致服务器崩溃。使用线程池可以进行统一的分配、监控和调优。
线程池的实现原理
当一个任务提交至线程池时,线程池是如何处理的呢?看如下主要流程图。
线程池的主要处理流程(图片来自《java并发编程的艺术》)
从图中可看成,线程池的处理流程如下:
- 线程池会先判断核心线程池是否已经满了,即判断核心线程池中的线程是否都在执行任务,如果没满,则创建一个新的线程来执行任务。否则,进行下一步。
- 判断存储任务的工作队列是否已经满了(前提是工作队列是有界的),若没满,则把任务存储在工作队列中等待核心线程池处理。否则,进行下一步。
- 判断线程池的最大数量线程池是否满了,若没满,则创建新的线程执行任务。否则,进行下一步。
- 对于无法执行的任务,按照某种饱和策略处理。
处理流程中的几个关键词:
- 核心线程池(corePoolSize)
提交一个任务到线程池时,线程池会创建一个新的线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,一直等到需要执行的线程数大于核心线程池的大小时就不再创建了。当然,java的线程池中提供了提前创建全部线程的方法,一旦调用,线程池会提前创建并启动。 - 工作队列(runnableTaskQueue)
用来保存等待执行的任务的阻塞队列。有几种选择:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue。 - 最大数量线程池(maximumPoolSize)
线程池允许创建的最大线程数量。如果工作队列满了,并且已运行的线程数小于最大线程数,则会创建新的线程执行任务。值得一提的是,如果工作队列是无界的队列,那么最大线程数将无意义。 - 饱和策略(RejectedExecutionHandler)
当工作队列和线程池都满了,说明线程池处于了饱和状态,那么就采取一种饱和策略来处理提交的新任务。Java5中线程池框架中提供了以下4种策略:
·AbortPolicy:直接抛出异常。
·CallerRunsPolicy:只用调用者所在线程来运行任务。
·DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。 ·DiscardPolicy:不处理,丢弃掉。
当然,也可以根据需要实现RejectedExecutionHandler接口来自定义策略。
下面看JDK中Executor线程池框架的实现
Executor框架的类与接口(图来自《Java并发编程的艺术》)
ThreadPoolExecutor是其核心实现类,看源码
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
......
}
构造方法中需要传以上参数,包括核心线程数、最大线程数、最长等待时间(当线程的数量大于核心线程数,空闲线程等待新的任务的最长时间)、工作队列、饱和策略等。通过对线程池合理的配置,才能使线程池发挥最好的效果。
这里,我只是简单的探讨了线程池的实现原理,当然,懂得了原理也要会用才能真正掌握一门技术。下一篇将探讨线程池的合理配置以及使用问题。