java并发编程(五)任务执行

接《java并发编程(四)同步工具类

悲剧的一天,本来想好好整理下,准备开始干《HTTP权威指南》,结果宿舍太吵,现在开始整理关于并发的知识吧,还是要淡定。

Executor框架

Executor基于生产者-消费者模式,提交任务的操作相当于生产者,执行任务的线程相当于消费者。

基于Executor的Web服务器

public class TaskExecutorWebServer {

	private static final int NTHREADS = 100;
	private static final Executor executor = Executors.newFixedThreadPool(NTHREADS);

	public static void main(String[] args) throws IOException, InterruptedException {

		ServerSocket serverSocket = new ServerSocket(8080);
		while (true) {
			final Socket socket = serverSocket.accept();
			Runnable task = new Runnable() {

				@Override
				public void run() {
					// TODO
				}
			};
			executor.execute(task);
		}
	}
}

我们可以很容易的将TaskExecutionWebServer 修改为类似Threa的PerTaskWebServer的行为,只需使用一个为每个请求都创建新线程的Executor。编写这样的Executor很简单,如程序所示。
public class ThreadPerTaskExecutor implements Executor {

	@Override
	public void execute(Runnable command) {
		new Thread(command).start();
	}
	
}
同样,还可以编写一个Executor使TaskExecutionWebServer的行为类似于单线程的行为,即以同步的方式执行每个任务,然后再返回。
public class WithinThreadExecutor implements Executor {

	@Override
	public void execute(Runnable command) {
		command.run();
	}
	
}


执行策略

任务的执行考虑下面几个问题:
  • 在什么线程中执行任务,
  • 任务按照什么顺序执行。
  • 多少个任务能并发执行。
  • 在队列中有多少个任务执行。
  • 如果系统由于过载而需要拒绝一个任务,选择哪个任务,如何通知应用程序有任务拒绝。
  • 在一个任务执行前后应该执行什么操作。

当看到下面这种形式的代码:


        new Thread(runnable).start();


并且你想获得一个更灵活的执行策略时,请认真考虑使用Executor来代替Thread


线程池

线程池是指管理一组同构工作的资源池。
类库提供了一个灵活的线程以及一些有用的默认配置。可以通过Executors中的静态工厂方法创建一个线程池。
  • newFixedThreadPool: 创建一个固定大小的线程池
  • newCachedThreadPool: 创建一个可缓存的线程池。
  • newSingleThreadExecutor: 单线程执行任务,
  • newScheduledThreadPool: 延时或定时执行任务,
  Executor以异步方式进行执行任务,因此在任何时刻,之前提交任务的状态不是立即可见的。任务可能处在  已经完成,正在运行,在关闭应用程序的时候,可能采取平缓的关闭方式。

为了解决执行服务的生命周期问题,Executor扩展了ExecutorService借口,添加了一些生命周期的方法。
shutdown(); 平缓关闭
isShutdown();
isTerminated();
awaitTermination(long timeout,TimeUnit unit);


一个可关闭的webserver
public class LifecycleWebServer {

	private final ExecutorService exec = Executors.newCachedThreadPool();

	public void start() throws IOException {
		ServerSocket socket = new ServerSocket(80);
		while (!exec.isShutdown()) {
			try {
				final Socket conn = socket.accept();
				exec.execute(new Runnable() {

					@Override
					public void run() {
						// TODO Auto-generated method stub

					}
				});
			} catch (RejectedExecutionException e) {
				if (!exec.isTerminated())
					log(e);
			}
		}

	}

	public void stop() {
		exec.shutdown();
	}

}

CompletionService

CompletionService将Executor和BlockingQueue的功能融合在一起,可以将Callable任务提交(submit)给他执行,然后使用类似于队列操作的take河poll等方法获得已经完成的结果,这些结果将封装成Future。ExecutorCompletion实现了CompletionService将计算结果委托给一个Executor。

通过源码分析发现,ExecutorCompletionService在构造函数中创建一个BlockingQueue 来保存计算完成的结果。当计算完成时,调用Future-Taks中的done方法。提交某个任务时,该任务将首先包装成一个QueueingFuture,这是一个子类,然后再改写子类的done方法,并将结果放入BlockingQueue中。

public class Renderer {
	private final ExecutorService executorService;

	public Renderer(ExecutorService executorService) {
		this.executorService = executorService;
	}

	void renderPage(CharSequence source) {

		List<ImageInfo> info = scanForImageInfo(source);
		CompletionService<ImageData> completionService = new ExecutorCompletionService<ImageData>(
				executorService);
		for (ImageInfo imageInfo : info) {
			completionService.submit(new Callable<ImageData>() {

				@Override
				public ImageData call() throws Exception {
					return imageInfo.downloadImage();
				}
			});
		}

		renderText(source);

		try {
			for (int t = 0, n = info.size(); t < n; t++) {
				Future<ImageData> f = completionService.take();
				ImageData imagData = f.get();
				renderImage(imagData);

			}
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
		} catch (ExecutionException e) {
			throw launderThrowable(e.getCause());
		}

	}

}

ExecutorCompletionService源码中QueueingFuture继承FutureTask重写了done方法。将完成的结果保存到completionQueue(LinkedBlockingQueue),所以事例中 可以在for中使用completionService.take(),实现异步加载。


一个设定时限的广告,当超过预设的时间,将默认的广告加入到页面

	Page renderWithAd() {
		long endNanos = System.nanoTime() + TIME_BUDGET;

		Future<Ad> f = exec.submit(new FetchAdTask());
		Page page = renderPageBody();

		Ad ad;
		try {
			long timeLeft = endNanos - System.nanoTime();
			ad = f.get(timeLeft, TimeUnit.SECONDS);

		} catch (ExecutionException e) {
			ad = DEFAULT_AD;
		} catch (Exception e) {
			ad = DEFAULT_AD;
			f.cancel(true);
		}
		page.add(ad);
		return page;
	}


一个不错的例子,可以用CompletionService实现 ,这里用了invokeAll方法,更加简单,可以将精力放在业务逻辑上。


class QuoteTask implements Callable<TravelQuote> {
	private final TravelCompany company;
	private final TravelInfo travelInfo;
        ...
	public TravelQuote call() throws Exception {
		return company.solicitQuote(travelInfo);
	}
}

getRankedTravelQuotes方法对异常进行了处理 值得借鉴。。。

try {

quotes.add(f.get());

} catch (ExecutionException e) {

quotes.add(task.getFailureQuote(e.getCause()));

} catch (CancellationException e) {

quotes.add(task.getTimeoutQuote(e));

}


	public List<TravelQuote> getRankedTravelQuotes(TravelInfo travelInfo,Set<TravelCompany> companies, Comparator<TravelQuote> ranking,long time, TimeUnit unit)
 throws InterruptedException {

		List<QuoteTask> tasks = new ArrayList<QuoteTask>();
		for (TravelCompany company : companies)
			tasks.add(new QuoteTask(company, travelInfo));
		List<Future<TravelQuote>> futures = exec.invokeAll(tasks, time, unit);
		List<TravelQuote> quotes = new ArrayList<TravelQuote>(tasks.size());
		Iterator<QuoteTask> taskIter = tasks.iterator();
		for (Future<TravelQuote> f : futures) {
			QuoteTask task = taskIter.next();
			try {
				quotes.add(f.get());
			} catch (ExecutionException e) {
				quotes.add(task.getFailureQuote(e.getCause()));
			} catch (CancellationException e) {
				quotes.add(task.getTimeoutQuote(e));
			}
		}
		Collections.sort(quotes, ranking);
		return quotes;
	}

Executor 框架简化了开发过程,将任务提交与执行策略解耦合,很多工具类源码值得分析,分析下设计的模式,设计的很巧妙。阅读源码很是有好处的,分析CompletionService 感觉自己设计的不足,看来设计模式没有融会贯通啊。










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值