创建线程执行器
Executor Framework概述
Java5引入了执行器框架(Executor Framework),这套框架分离了任务的创建和执行。使用执行器,仅需要实现Runnable接口的对象,然后将这些对象发送给执行器即可。执行器通过创建所需的线程,来负责这些Runnable对象的创建、实例化以及运行。而且,执行器使用了线程池来提高应用程序性能。当发送一个任务给执行器时,执行器会尝试使用线程池中的线程来执行这个任务,避免了不断地创建和销毁线程而导致系统吸能下降。
执行器的另一个重要优势是Callable接口。它类似于Runnable接口,但提供个两方面的增强:
- Callable接口的主方法是call,可以返回结果。
- 当发送一个Callable对象给执行器时,将获得一个实现了Future接口的对象。可以使用这个对象来控制Callable对象的状态和结果。
创建执行器
Executor Framework是由围绕着Executor和子接口ExecutorService的接口和类组成。主要使用的实现类是ThreadPoolExecutor类。ThreadPoolExecutor提供了4个重载的构造函数,这4个构造函数参数都比较复杂,所以一般都是通过工厂Executors的静态方法来创建。比如使用Executors的newCachedThreadPool方法创建一个缓存线程池的执行器,使用Executors.newFixedThreadPool方法创建固定大小的线程执行器。
创建缓存线程池执行器示例
下面用示例说明如何创建缓存线程池执行器:
public class CachedThreadPoolDemo {
public static void main(String[] args){
ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newCachedThreadPool();
for(int i=0; i<10; i++){
DummayTask task = new DummayTask();
executor.execute(task);
}
executor.shutdown();
}
}
class DummayTask implements Runnable{
private Date initDate;
DummayTask() {
this.initDate = new Date();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":任务创建时间:" + initDate);
System.out.println(Thread.currentThread().getName() + ":任务开始时间:" + new Date());
long delay = (long)(Math.random()*1000);
try {
Thread.sleep(delay);
System.out.println(Thread.currentThread().getName() + ":任务完成时间:" + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行日志:
pool-1-thread-2:任务创建时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-2:任务开始时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-6:任务创建时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-1:任务创建时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-4:任务创建时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-8:任务创建时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-4:任务开始时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-3:任务创建时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-3:任务开始时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-7:任务创建时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-7:任务开始时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-8:任务开始时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-5:任务创建时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-5:任务开始时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-1:任务开始时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-10:任务创建时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-10:任务开始时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-6:任务开始时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-9:任务创建时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-9:任务开始时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-5:任务完成时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-6:任务完成时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-4:任务完成时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-10:任务完成时间:Mon Apr 21 16:06:28 CST 2014
pool-1-thread-3:任务完成时间:Mon Apr 21 16:06:29 CST 2014
pool-1-thread-9:任务完成时间:Mon Apr 21 16:06:29 CST 2014
pool-1-thread-7:任务完成时间:Mon Apr 21 16:06:29 CST 2014
pool-1-thread-2:任务完成时间:Mon Apr 21 16:06:29 CST 2014
pool-1-thread-8:任务完成时间:Mon Apr 21 16:06:29 CST 2014
pool-1-thread-1:任务完成时间:Mon Apr 21 16:06:29 CST 2014
创建固定大小的线程执行器示例
上例显示了缓存线程池执行器的使用方法,当需要执行新任务时,缓存线程池执行器就会创建新线程来执行,只有线程所运行的任务执行完成后并且这个线程可用,才会重用这些线程。这种机制情况下,如果发给过多任务将会使系统负荷过载。为避免这个问题,可以使用固定大小的线程执行器。这个执行器有一个线程最大值,如果发送超过这个最大值的任务,执行器将不再创建额外线程,剩下的任务将被阻塞直到执行器有空闲的线程可用。这样可以保证执行器不会是系统的负荷过载。下面示例演示了固定大小的线程执行器使用方法:
public class FixedThreadPoolDemo {
public static void main(String[] args){
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(3);
for(int i=0; i<10; i++){
DummayTask task = new DummayTask();
executor.execute(task);
System.out.println("main:执行器线程数:" + executor.getActiveCount());
}
executor.shutdown();
}
}
程序运行日志:
main:执行器线程数:1
main:执行器线程数:2
main:执行器线程数:3
main:执行器线程数:3
main:执行器线程数:3
main:执行器线程数:3
main:执行器线程数:3
main:执行器线程数:3
main:执行器线程数:3
main:执行器线程数:3
pool-1-thread-2:任务创建时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-2:任务开始时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-3:任务创建时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-3:任务开始时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-1:任务创建时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-1:任务开始时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-1:任务完成时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-1:任务创建时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-1:任务开始时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-2:任务完成时间:Mon Apr 21 16:18:03 CST 2014
pool-1-thread-2:任务创建时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-2:任务开始时间:Mon Apr 21 16:18:03 CST 2014
pool-1-thread-3:任务完成时间:Mon Apr 21 16:18:03 CST 2014
pool-1-thread-3:任务创建时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-3:任务开始时间:Mon Apr 21 16:18:03 CST 2014
pool-1-thread-1:任务完成时间:Mon Apr 21 16:18:03 CST 2014
pool-1-thread-1:任务创建时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-1:任务开始时间:Mon Apr 21 16:18:03 CST 2014
pool-1-thread-3:任务完成时间:Mon Apr 21 16:18:03 CST 2014
pool-1-thread-3:任务创建时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-3:任务开始时间:Mon Apr 21 16:18:03 CST 2014
pool-1-thread-2:任务完成时间:Mon Apr 21 16:18:03 CST 2014
pool-1-thread-2:任务创建时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-2:任务开始时间:Mon Apr 21 16:18:03 CST 2014
pool-1-thread-3:任务完成时间:Mon Apr 21 16:18:03 CST 2014
pool-1-thread-3:任务创建时间:Mon Apr 21 16:18:02 CST 2014
pool-1-thread-3:任务开始时间:Mon Apr 21 16:18:03 CST 2014
pool-1-thread-2:任务完成时间:Mon Apr 21 16:18:04 CST 2014
pool-1-thread-1:任务完成时间:Mon Apr 21 16:18:04 CST 2014
pool-1-thread-3:任务完成时间:Mon Apr 21 16:18:04 CST 2014
可以看出,执行器运行的线程数没有超过3个。