合理利用线程池能够带来三个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
我们可以通过 ThreadPoolExecutor 来创建一个线程池。
1: public ThreadPoolExecutor(int corePoolSize,
2: int maximumPoolSize,
3: long keepAliveTime,
4: TimeUnit unit,
5: BlockingQueue<Runnable> workQueue) {
其中corePoolSize表示线程池的基本大小:当提交一个任务到线程池时,线程池会创建一个线程来执行任务, 即使其他空闲的基本线程能够执行新任务也会创建线程, 等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的 prestartAllCoreThreads 方法,线程池会提前创建并启动所有基本线程。
maximumPoolSize是线程池最大大小,线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。
workQueue任务队列,用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。
1) ArrayBlockingQueue: 是一个基于数组结构的有界阻塞队列, 此队列按 FIFO(先进先出)原则对元素进行排序。
2) LinkedBlockingQueue: 一个基于链表结构的阻塞队列, 此队列按 FIFO (先进先出) 排序元素, 吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
3) SynchronousQueue: 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态, 吞吐量通常要高于 LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。
4) PriorityBlockingQueue: 一个具有优先级得无限阻塞队列。
我们可以使用 execute 提交的任务,但是 execute 方法没有返回值,所以无法判断任务知否被线程池执行成功。
1: threadPoolExecutor.execute(new Runnable(){
2: @Override
3: public void run() {
4: // TODO Auto-generated method stub
5: }
6: });
我们也可以使用 submit 方法来提交任务,它会返回一个 future, 通过这个 future 来判断任务是否执行成功,通过 future 的 get 方法来获取返回值, get 方法会阻塞住直到任务完成,而使用get(long timeout, TimeUnit unit)方法则会阻塞一段时间后立即返回,这时有可能任务没有执行完。
1: Future<Object> future = executor.submit(harReturnValuetask);
2: try {
3: Object s = future.get();
4: } catch (InterruptedException e) {
5: // 处理中断异常
6: } catch (ExecutionException e) {
7: // 处理无法执行任务异常
8: } finally {
9: // 关闭线程池
10: executor.shutdown();
11: }
12:
我们可以通过调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池,它们的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程,所以无法响应中断的任务可能永远无法终止。 但是它们存在一定的的区别, shutdownNow 首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而 shutdown 只是将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。
只要调用了这两个关闭方法的其中一个, isShutdown 方法就会返回 true。当所有的任务都已关闭后, 才表示线程池关闭成功,这时调用 isTerminaed 方法会返回 true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown 来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。
线程池的主要工作流程如图:
从上图我们可以看出,当提交一个新任务到线程池时,线程池的处理流程如下:
1) 首先线程池判断基本线程池是否已满?没满, 创建一个工作线程来执行任务。 满了,则进入下个流程。
2) 其次线程池判断工作队列 是否已满?没满,则将新提交的任务存储在工作队列里。 满了,则进入下个流程。
3) 最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。
1:
2: public void execute(Runnable command) {
3: if (command == null)
4: throw new NullPointerException();
5: /*
6: * Proceed in 3 steps:
7: *
8: * 1. If fewer than corePoolSize threads are running, try to start a new thread with the given command as its first task. The call to addWorker atomically checks runState and workerCount, and so prevents false alarms that would add threads when it shouldn't, by returning false.
9: * 2. If a task can be successfully queued, then we still need to double-check whether we should have added a thread (because existing ones died since last checking) or that the pool shut down since entry into this method. So we recheck state and if necessary roll back the enqueuing if stopped, or start a new thread if there are none.
10: * 3. If we cannot queue task, then we try to add a new thread. If it fails, we know we are shut down or saturated and so reject the task.
11: */
12: int c = ctl.get();
13: if (workerCountOf(c) < corePoolSize) {
14: if (addWorker(command, true))
15: return;
16: c = ctl.get();
17: }
18: if (isRunning(c) && workQueue.offer(command)) {
19: int recheck = ctl.get();
20: if (! isRunning(recheck) && remove(command))
21: reject(command);
22: else if (workerCountOf(recheck) == 0)
23: addWorker(null, false);
24: }
25: else if (!addWorker(command, false))
26: reject(command);
27: }