转载请注明出处: jiq钦's technical Blog - 季义钦
在Java 5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。此外还有一个FutureTask任务类型以拓展Runnable和Callable。
(备注:图片转自网络)
一、Runnable任务抽象
考虑一个处理客户端请求的服务器程序:
1.1 Thread方式
通常我们为了提高服务器监听程序的响应能力,并不会在监听主程序中处理接收到的客户端请求,而是在接收到请求之后为其分配一个线程去处理:
class ThreadPerTaskWebServer{
publicstatic void main(String[] args) throws IOException{
ServerSocketsocket = new ServerSocket(8080);
while(true){
fnalSocket connnection = new socket.accept();
Runnabletask = new Runable(){ //匿名类
publicvoid run(){
handleClientRequest(connection);
}
};
new Thread(task).start();
//or rask.run();
}
}
}
1.2 线程池
1.2.1 线程池抽象Executor
生产环境中,“为每个请求分配一个线程”这种方法存在一定缺陷,尤其是当需要创建大量线程时,主要体现在几个方面:
A 频繁创建和销毁线程代价非常高;
B 过多线程极耗内存。当你有足够多的线程保持CPU忙碌时,更多的线程反而降低性能;
C 稳定性问题。可创建线程数受JVM参数、栈大小等限制,无限制创建线程可能带来异常;
由此java推出了线程池的库,任务执行的抽象从Thread变为Executor,Executor描述的是一个具备特定执行策略的线程池,应用程序只需要简单地将任务提交到线程池,具体的执行策略由具体的线程池实现决定。
class TaskExecutionWebServer{
privatestatic final int SIZE = 100;
privatestatic final Executor exec = Executors.newFixedThreadPool(SIZE);
publicstatic void main(String[] args) throws IOException{
ServerSocketsocket = new ServerSocket(8080);
while(true){
finalSocket connection = socket.accept();
Runnabletask = new Runable(){
publicvoid run(){
handleClientRequest(connection);
}
};
exec.execute(task);
}
}
}
Executor接口较为简单,仅仅包含一个执行接口:
public interface Executor {
voidexecute(Runnable command); //特别注意仅仅支持执行Runnable
}
注意我们可以采用不同的Executor实现,来改变服务器执行任务的策略。
比如我们实现一个和前面一样无限制创建线程并启动的Executor线程池:
public class ThreadPerTaskExecutorimplements Executor {
publicvoid execute(Runnable r){
newThread(r).start();
}
}
1.2.2 带生命周期管理的线程池ExecutorService
为了解决执行服务的生命周期问题,Executor扩展出了ExecutorService接口,添加了一些生命周期管理的方法。并且能够返回一个可以追踪异步任务进度的Future对象。
public interface ExecutorService extends Executor {
voidshutdown();
List<Runnable> shutdownNow();
booleanisShutdown();
booleanisTerminated();
booleanawaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
<T>Future<T> submit(Callable<T> task); //返回任务的Future,可取消和获取结果
Future<?>submit(Runnable task); //返回任务的Future,可取消
}
ExecutorService创建时处于运行状态。当调用ExecutorService.shutdown()后,处于关闭状态,isShutdown()方法返回true。这时,不应该再向ExecutorService中添加任务,所有已添加的任务执行完毕后,ExecutorService处于终止状态,isTerminated()返回true。
异步计算结果Future
对于一个提交到线程池的任务,其执行可能需要很长的时间,有时候希望能够取消这些任务。Future表示一个指定任务的生命周期,提供了对应的方法判断一个任务是否已经完成或取消,以及提供获取任务的结果、取消任务的方法。
public interface Future<V> {
booleancancel(boolean mayInterruptIfRunning);
booleanisCancelled();
boolean isDone();
V get() throwsInterruptedException, ExecutionException;
V get(longtimeout, TimeUnit unit)
throwsInterruptedException, ExecutionException, TimeoutException;
}
任务可取消条件:
1、 已提交尚未开始执行的任务
2、 已经开始执行的任务需要能够响应中断才能够取消
1.2.3 延迟和周期性执行的线程池ScheduledExecutorService
ScheduledExecutorService拓展了ExecutorService接口,增加类似于Timer的功能,而且也解决了Timer的缺陷,比如Timer在执行所有定时任务时只有一个线程,如果某个任务时间较长,那么将破坏其他TimerTask的定时精确性。另外如果TimerTask抛出一个未检查的异常,Timer并不捕获异常,此时定时器线程将终止。
public interface ScheduledExecutorService extendsExecutorService {
publicScheduledFuture<?> schedule(Runnable command, long delay, TimeUnitunit);
public <V>ScheduledFuture<V> schedule(Callable<V> callable, long delay,TimeUnit unit);
}
1.2.4 Executors工具类
Java提供了一个工具类Executors来获取上述默认的线程池实现。
public static ExecutorService newFixedThreadPool(int nThreads) 创建固定数目线程的线程池。 |
public static ExecutorService newCachedThreadPool() 创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。 如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。 |
public static ExecutorService newSingleThreadExecutor() 创建一个单线程化的Executor。 |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。 |
二、Callable任务抽象
Executor框架使用Runnable作为其基本的任务表示形式。但是Runnable有很大局限:
1、 不能返回一个值
2、 不能抛出一个受检查的异常
实际上许多任务都存在延迟的计算:执行数据库查询、从网络上获取资源、或者计算某个复杂的功能,对于这些任务,一个带返回值的Callable任务是一个更好的抽象。它认为任务的主入口即call()将返回一个值,或者可能抛出一个异常。
2.1 ExecutorService执行
<执行一个带返回值的Callable任务>
ExecutorService线程池提供如下方法提交一个Callable任务:
<T> Future<T> submit(Callable<T> task);
下面是一个页面渲染的例子,将页面渲染分为两个任务:主线程渲染文本,另一个线程渲染所有下载的图像。
public class FutureRender{
privatefinal ExecutorService executor = . . . ;
voidrenderPage(CharSequence source){
finalList<ImageInfo> imageInfos = scanForImage(source);
Callable<List<Image>>task = new Callable<List<Image>>(){
publicList<Image> call(){
List<Image>result = new ArrayList<Image>();
for(ImageInfoinfo : imageInfos)
result.add(info.downloadImage());//串行下载所有图像
returnresult;
}
};
Future<List<Image>>future = executor.submit(task); //提交任务
renderText(source);
try{
List<Image>imageData = future.get(); //阻塞获取任务执行结果
for(Image image :imageData)
renderImage(image);
}catch(InterruptedException ee)
{
Thread.currentThread().interrupt();//重新设置中断状态
future.cancel(true); //线程被中断,所以取消下载任务
} catch(Exception ee)
{
}
}
}
2.2 CompletionService执行
<执行一组待返回值的Callable任务>
当待返回值的任务抽象Callable交给ExecutorService线程池来执行时,某些场景下将会不适用。考虑这样的场景,如果需要向Executor提交一组计算Callable任务,并且希望在计算完成之后获取所有计算任务的计算结果,比如上面渲染页面的例子中,不是开启一个计算任务下载所有图像,而是针对每个图像都开启一个下载任务。
这个时候就需要维护所有提交的下载图像的计算任务的Future引用,在获取结果时循环获取这一组计算任务的执行结果。
很明显这样比较繁琐,因此Java提供了CompletionService,将Executor和BlockingQueue的功能封装在一起,我们尽管将一组Callable计算任务都提交给CompletionService,只需要维护一个CompletionService线程池对象,最后需要这组任务的计算结果时,只需要通过这个维护的CompletionService对象循环取出(出队)即可。
CompletionService提供take方法按完成顺序返回任务的结果,CompletionService内部维护了一个阻塞队列BlockingQueue,如果没有任务完成,take()方法也会阻塞。
public interface CompletionService<V> {
Future<V>submit(Callable<V> task);
Future<V>submit(Runnable task, V result);
Future<V>take() throws InterruptedException;
Future<V>poll();
Future<V>poll(long timeout, TimeUnit unit) throws InterruptedException;
}
ExecutorCompletionService是CompletionService的一个具体实现类,可以看到其构造函数要求传入一个Executor线程池,ExecutorCompletionService就是为传入的线程池封装利用队列存储一组任务的执行结果的功能。
public class ExecutorCompletionService<V> implementsCompletionService<V> {
publicExecutorCompletionService(Executor executor) {
}
再看上面那个例子,将其改为针对每个图像的下载都分配一个计算任务:
public class FutureRender{
privatefinal ExecutorService executor = . . . ;
voidrenderPage(CharSequence source){
finalList<ImageInfo> imageInfos = scanForImage(source);
//封装executor
CompletionService<Image>completionService = new
ExecutorCompletionService<Image>(executor);
//为每个图像下载都提交一个Callable任务
for(final ImageInfoinfo : imageInfos)
completionService.submit(newCallable<Image>(){
publicList<Image> call(){
returninfo.downloadImage();
}
});
renderText(source);
try{
for(inti=0,n=imageInfos.size();i<n;i++){
Future<Image>f = completionService.take(); //将完成的Future取出
Image image = f.get(); //获取计算结果
renderImage(image);
}
}catch(InterruptedException ee)
{
Thread.currentThread().interrupt();//重新设置中断状态
future.cancel(true); //线程被中断,所以取消下载任务
} catch(Exception ee)
{
}
}
}
三、FutureTask任务抽象
FutureTask描述一个可取消的异步计算任务。
public class FutureTask<V> implementsRunnableFuture<V> {
publicFutureTask(Callable<V> callable) { }
publicFutureTask(Runnable runnable, V result) { }
}
其中RunnableFuture接口继承自Runnable和Future接口:
public interface RunnableFuture<V> extends Runnable,Future<V> {
void run();
}
从类定义也可以看出,FutureTask是另一种Runnable抽象,这种Runnable抽象具备Future的可获取执行结果和可取消的特性。
Runnable和Callable都可以包装成FutureTask,让其变为Runnable和Future类型。
3.1 将Callable包装成FutureTask
让Callable任务既可以作为Runnable被执行,又可以具备Future的可取消、可获取执行结果的特性。
特别注意:让Callable任务可以作为Runnable任务执行,代表其可以交给自定义Executor线程池执行,也可以像Runnable那样直接start或者run。
3.2 将Runnable包装成FutureTask
让Runnable任务既可以作为Runnable被执行,又可以具备Future的可取消的特性。