线程池的工作原理及使用示例
. 为什么要使用线程池?
我们现在考虑最简单的服务器工作模型:服务器每当接收到一个客户端请求时就创建一个线程为其服务。这种模式理论上可以工作的很好,但实际上会存在一些缺陷,服务器应用程序中经常出现的情况是单个客户端请求处理的任务很简单但客户端的数目却是巨大的,因此服务器在创建和销毁线程所花费的时间和系统资源可能比处理客户端请求处理的任务花费的时间和资源更多。
线程池技术就是为了解决上述问题而出现的。合理的使用线程池便可重复利用已创建的线程,以减少在创建线程和销毁线程上花费的时间和资源。除此之外,线程池在某些情况下还能动态的调整工作线程的数量,以平衡资源消耗和工作效率。同时线程池还提供了对池中工作线程进行统一的管理的相关方法。
2. 线程池的简要工作模型
线程池的工作模型主要两部分组成,一部分是运行Runnable的Thread对象,另一部分就是阻塞队列。
由线程池创建的Thread对象其内部的run方法会通过阻塞队列的take方法获取一个Runnable对象,然后执行这个Runnable对象的run方法(即,在Thread的run方法中调用Runnable对象的run方法)。当Runnable对象的run方法执行完毕以后,Thread中的run方法又循环的从阻塞队列中获取下一个Runnable对象继续执行。这样就实现了Thread对象的重复利用,也就减少了创建线程和销毁线程所消耗的资源。
当需要向线程池提交任务时会调用阻塞队列的offer方法向队列的尾部添加任务。提交的任务实际上就是是Runnable对象或Callable对象。
上述仅仅是最简略的线程池工作模型,但体现了线程池的核心思想,而至于线程池中线程的动态的创建和自行销毁、动态调整实际工作的线程数、阻塞队列的排队策略以及队列的长度等等细节问题会在本博客中“线程池 ThreadPoolExecutor、Executors源代码分析”的文章中详细介绍。
3. Executor、ExecutorService、AbstractExecutorService、ThreadPoolExecutor、Executors之间的关系
可以看出ExecutorService接口定义了线程池应该具有的行为特征(图中没有列出了ExecutorsService的全部方法),而真正实现线程池的是ThreadPoolExecutor类。由于ThreadPoolExecutor的构造函数参数众多且某些参数又会对线程的工作有着重大的影响。为了防止使用者错误搭配ThreadPoolExecutor构造函数的各个参数以及更加方便简洁的创建ThreadPoolExecutor对象,JavaSE中又定义了Executors类,Eexcutors类提供了创建常用配置线程池的方法。
4. ExecutorService接口介绍
ExecutorService接口定义了线程池应该具有的行为特征,它具有以下主要方法
123456789101112void
shutdown();
List<Runnable> shutdownNow();
boolean
isTerminated();
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<?
extends
Callable<T>> tasks)
throws
InterruptedException;
<T> T invokeAny(Collection<?
extends
Callable<T>> tasks)
throws
InterruptedException, ExecutionException;
submit相关方法:向线程池添加执行的任务
shutdown方法:此方法执行后不得向线程池再提交任务,如果有空闲线程则销毁空闲线程,等待所有正在执行的任务及位于阻塞队列中的任务执行结束,然后销毁所有线程。
shutdownNow方法:此方法执行后不得向线程池再提交任务,如果有空闲线程则销毁空闲线程,取消所有位于阻塞队列中的任务,并将其放入List<Runnable>容器,作为返回值。取消正在执行的线程(实际上仅仅是设置正在执行线程的中断标志位)。
invokeAll方法:一次性向线程池提交多个任务,并返回全部结果。
invokeAny方法:一次性向线程池提交多个任务,并将第一个得到的结果作为返回值,然后立刻取消所有正在执行的线程。
isTerminated方法:池中的线程全部销毁后,该方法返回真,否则返回假。
由于ExecutorService继承了Executor接口,所以它其实还具有一个execute方法,它的功能是向线程池提交一个Runnable对象的任务。submit方法中就调用了execute方法,不过submit在调用execute方法之前将Runnable对象或Callable对象包装成了RunnableFuture对象,然后交由execute来提交任务。
5. 线程池使用示例
package javalearning; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolDemo { static class Task implements Runnable{ private String id; Task(String id){ this.id = id; } @Override public void run() { System.out.println("Thread "+id+" is working"); try { //每个任务随机延时1s以内的时间以模拟线程的运行 Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread "+id+" over"); } } public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(3);//线程池中,3工作线程 threadPool.execute(new Task("a")); threadPool.execute(new Task("b")); threadPool.execute(new Task("c")); threadPool.execute(new Task("d")); threadPool.execute(new Task("e")); threadPool.shutdown(); while(!threadPool.isTerminated()){ } System.out.println("Thread Pool is over"); } }
从执行结果可以看出,同时最多有三个线程并发执行,超过三个任务以后,线程池会将任务存储于阻塞队列中。
Thread a is working Thread c is working Thread b is working Thread a over Thread d is working Thread d over Thread e is working Thread e over Thread b over Thread c over Thread Pool is over