系统启动一个新线程的成本是比较高的,因为它涉及与操作系统交互。在这种情况下,使用线程池可以很好的提高性能,尤其是当程序中需要创建大量存在周期很短暂的线程时,更应该考虑线程池。
与数据库连接池比较相似的是,线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象或者Callable对象传递给线程池,线程池就会启动一个线程来执行它们的run方法或者call方法。当run方法或者call方法执行结束后,线程并不会死亡,而是再次返回线程池成为一个空闲线程,等待执行下一个Runnable的run方法或者Callable对象的call方法。
除此之外,使用线程池可以有效的控制系统中并发线程的数量。当系统中包含大量并发线程时,会导致系统性能剧烈下降,甚至导致JVM崩溃,而线程池最大线程数参数可以控制系统中并发线程的数量不会超过此数。.
总结来说,通过线程池我们可以提高性能以及控制内存开销,下边我们来说一下使用线程池的一般步骤:
- 使用Executors类的静态方法创建ExecutorService对象,这个对象代表一个线程池。
- 使用这个线程池对象的execute或submit方法来提交一个任务,而这个任务就是一个线程
- 当要关闭线程池的时候,可以使用线程池对象ExecutorService的shutdown方法
需要注意的是Java共提供四种线程池,分别是:
- newCachedThreadPoo 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
下面以第二种线程池为例,来展示一下如何使用线程池。
public class TestFiexedThreadPool {
public static void main(String[] args) {
//创建一个固定大小的线程池,最多只能产生4个线程
ExecutorService threadPool = Executors.newFixedThreadPool(4);
final Random r = new Random();
for (int i = 0; i < 10; i++) {
final int task = i;
//使用这个线程池执行一个任务
threadPool.execute(new Runnable() {
@Override
public void run() {
for (int k = 0; k < 10; k++) {
try {
Thread.sleep(r.nextInt(100));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("当前线程的名字:"+Thread.currentThread().getName()+"运行第"+task+"个任务");
}
}
});
}
}
}
这段程序创建了一个定长线程池,每当提交一个任务就创建一个线程,一直到最大长度。而这个最大长度也就是这个线程池中最大线程数量,本例中使用的4个。
另外,线程池有四种拒绝任务策略。
这里先假设一个前提:线程池有一个任务队列,用于缓存所有待处理的任务,正在处理的任务将从任务队列中移除。因此在任务队列长度有限的情况下就会出现新任务的拒绝处理问题,需要有一种策略来处理应该加入任务队列却因为队列已满无法加入的情况。另外在线程池关闭的时候也需要对任务加入队列操作进行额外的协调处理。
RejectedExecutionHandler提供了四种方式来处理任务拒绝策略
1、直接丢弃(DiscardPolicy)
2、丢弃队列中最老的任务(DiscardOldestPolicy)。
3、抛异常(AbortPolicy)
4、将任务分给调用线程来执行(CallerRunsPolicy)。
这四种策略是独立无关的,是对任务拒绝处理的四中表现形式。最简单的方式就是直接丢弃任务。但是却有两种方式,到底是该丢弃哪一个任务,比如可以丢弃当前将要加入队列的任务本身(DiscardPolicy)或者丢弃任务队列中最旧任务(DiscardOldestPolicy)。丢弃最旧任务也不是简单的丢弃最旧的任务,而是有一些额外的处理。除了丢弃任务还可以直接抛出一个异常(RejectedExecutionException),这是比较简单的方式。抛出异常的方式(AbortPolicy)尽管实现方式比较简单,但是由于抛出一个RuntimeException,因此会中断调用者的处理过程。除了抛出异常以外还可以不进入线程池执行,在这种方式(CallerRunsPolicy)中任务将有调用者线程去执行。