第六章 任务执行
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(); } } } }