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) { ...... }构造方法中需要传以上参数,包括核心线程数、最大线程数、最长等待时间(当线程的数量大于核心线程数,空闲线程等待新的任务的最长时间)、工作队列、饱和策略等。通过对线程池合理的配置,才能使线程池发挥最好的效果。 这里,我只是简单的探讨了线程池的实现原理,当然,懂得了原理也要会用才能真正掌握一门技术。下一篇将探讨线程池的合理配置以及使用问题。