第六章 任务执行
1. Executor框架
public interface Executor{
void execute(Runnable command);
}
该框架能支持多种不同类型的任务执行策略。它提供了一种标准的方法将任务的提交过程与执行过程解耦,并用Runnable来表示任务。Executor的实现还提供了对生命周期的支持,以及统计信息收集,应用程序管理机制和性能监视等机制。
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* 基于线程池的Web服务器
* @author cream
*
*/
public class TaskExecutionWebServer {
private static final int NTHREADS = 100;
private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);
public static void main(String[] args) throws IOException {
ServerSocket socket = new ServerSocket(80);
while(true){
final Socket connection = socket.accept();
Runnable task = new Runnable() {
public void run() {
//处理请求
}
};
exec.execute(task);
}
}
}
上述代码,通过使用Executor,将请求处理任务的提交与任务的实际执行解耦。
2. 线程池
线程池的优势:
1. 通过重用现有的线程而不是创建新线程,可以减少创建和销毁线程的开销
2. 当请求到来时,由于线程已经存在,可以减少等待时间,从而提高了响应性
可以通过调用Executors中的静态工厂方法之一来创建一个线程池:
newFixedThreadPool:创建一个定长的线程池, 每当提交一个任务就创建一个线程, 直到达到池的最大长度, 这时线程池会保持长度不再变化. (一任务一线程)
newCachedThreadPool :创建一个可缓存的线程池, 如果当前线程的长度超过了处理的需要时, 它可以灵活的回收空闲的线程, 当需求增加时, 它可以灵活的增加新的线程, 而并不会对池的长度做任何限制. (缓存线程池)
newSingleThreadExecutor :创建一个单线程化的executor, 它只创建唯一的工作者线程来执行任务, 如果这个线程异常结束, 会有另一个取代它, 但是任务会保存在一个queue中等待执行. (多任务一线程)
newScheduleThreadPool :创建一个定长的线程池, 而且支持定时的以及周期性执行任务, 类似timer.(定时线程池)
3.Executor的生命周期
为了解决执行服务的生命周期问题,Executor扩展了ExecutorService接口,添加了生命周期的管理方法
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
....
}
生命周期状态有三种:运行,关闭和已终止。
shuting down(关闭)状态:
shutdown:将停止接受新的任务, 同时等待已经提交的任务完成, 包括尚未完成的任务
showdownNow:会启动一个强制的关闭过程, 尝试取消所有运行中的任务和排在队列中尚未开始的任务,并把排队中尚未开始的任务返回。
对于关闭后提交到ExecutorService中的任务, 会被(拒接执行处理器)rejected execution handler处理,它会抛弃任务,或者使得execute方法抛出一个未检查的RejectedExecutionException。
terminated(已终止)状态:
等所有任务都完成之后,进入terminated状态, 可以调用awaitTermination等待ExecutorService到达终止状态, 也可以轮询检查isTerminated判断是否终止. 通常shutdown会紧随awaitTermination之后, 这样可以产生同步地关闭ExecutorService的效果.
4.携带结果的Future和Callable
Exceutor框架使用Runnable作为最基本的任务形式,但是Runnable有一种很大的局限性,它不能返回一个值或抛出一个受检查的异常。
Callable是一种更好的任务形式,它能返回(call)一个值或者抛出一个异常。
Future表示一个任务的生命周期,并提供了相应的方法来判断是否已经完成或取消,以及获取任务的结果和取消任务等。
public interface Callable<V>{
V call() throws Exception;
}
public interface Future<V>{
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException, CancellationException;
V get(long timeout,TimeUnit unit) throws InterruptedException, ExecutionException, CancellationException,TimeoutException;
}
get()方法的行为将取决于任务的状态,如果任务已经完成,那么get会立即返回结果或者抛出一个Exception,如果任务没有完成,那么get将阻塞并直到任务完成。如果任务抛出了异常,那么get将异常封装成ExecutionException并重新抛出,如果任务被取消,那么get抛出CancellationException。如果get抛出了ExecutionException,那么可以通过getCause获得被封装的初始异常。
ExecutorService中的所有submit方法都将返回一个Future,从而将一个Runnable或者Callable任务传递给ExecutorService的submit方法,返回一个Future用于获得任务的执行结果或者取消任务。
5. CompletionService: Executor与BlockingQueue
CompletionService用来将Executor与BlockingQueue进行结合, 将Callable任务提交给它执行, 然后使用类似队列中的take和poll在结果完整时获得这个结果。
public class Test {
private final ExecutorService executor = Executors.newCachedThreadPool();
void renderPage(String source) {
final List<ImageInfo> imageInfos = scanForImageInfo(source);
CompletionService<ImageData> service = new ExecutorCompletionService<ImageData>(
executor);
for (final ImageInfo imageInfo : imageInfos) {
service.submit(new Callable<ImageData>() {
public ImageData call() throws Exception {
return imageInfo.downloadImage();
}
});
}
renderText(source);
for (int i = 0; i < imageInfos.size(); i++) {
Future<ImageData> f;
try {
f = service.take();
ImageData imageData = f.get();
renderImage(imageData);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}