JavaConcurrencyInPractice-任务执行
1、任务
什么是任务?
任务通常是一组抽象且离散的工作单元,通过把程序的功能分解到多个任务,
可以简化程序结构,优化错误处理,以及提供自然的并行工作结构。
找出任务的边界:
在理想的情况下,任务应该是相互独立的,任务的独立有助于实现并发。
每一项任务都应该表示程序的一部分处理能力,以便方便实现调度和负载均衡。
大多数服务器应用程序都选择了以一个独立的客户请求为任务边界。
2、串行地执行任务
最简单的就是串行地处理任务,如在单个线程中串行地处理客户端请求。
但这通常无法提供高吞吐率和快速响应性。
class SingleThreadWebServer { public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(80); while (true) { Socket connection = socket.accept(); handleRequest(connection); } } }
3、并行处理-一个任务一个线程
既然串行处理无法利用任务潜在的串行性,那么,考虑采用并行的任务处理。
这使得:
1、任务接收与处理分开,一个主线程专门接收任务,创建子线程用于执行任务。
2、任务可以并行处理,提高了吞吐量。
3、任务处理代码必须是线程安全的,以保证多线程访问时的线程安全性。
class ThreadPerTaskWebServer { 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() { handleRequest(connection); } }; new Thread(task).start(); } } }
无限创建线程的不足:
1、线程的生命周期的开销是很高的,反复的创建和销毁是需要代价的。
2、线程时需要消耗资源的,如果线程的数量已经到达阈值,再去过多的创建线程也不能提高并行性。
3、稳定性,容易发生超出OutOfMemoryError,容易被恶意攻击(洪泛攻击)。
4、任务执行与任务执行策略的分离 - Executor框架
任务是一组逻辑工作单元,而线程则是任务异步执行的机制。
两者应该是分开的。不能把它们绑在一起。
任务的执行策略(机制)和任务的执行应该分离。
我们可以采用不同的策略去执行相同的任务(如串行、并行等)。
Executor: An object that executes submitted Runnable tasks. This interface provides a way of decoupling task submission from the mechanics of how each task will be run,
including details of thread use, scheduling, etc.
在Java类库中,任务执行的主要抽象是Executor而不是Thread,它提供了多种任务执行策略。
Executor提供了一种标准的方法将任务的提交过程与执行过程解耦开来,并用Runnable来表示任务。
只需要采用另一种Executor实现,就可以改变任务的执行策略,从而改变程序的行为。
/**
* 使用固定数量的线程来运行任务 FixedThreadPool
*/
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() { handleRequest(connection); } }; exec.execute(task); } } }
/** * 用Executor实现一个任务一个线程的方式 */ public class ThreadPerTaskExecutor implements Executor { public void execute(Runnable r) { new Thread(r).start(); }; }
/** * 用Executor实现串行执行 */ public class WithinThreadExecutor implements Executor { public void execute(Runnable r) { r.run(); }; }
在执行策略中,需要考虑:
1、In what thread will tasks be executed?
2、 In what order should tasks be executed (FIFO, LIFO, priority order)?
3、 How many tasks may execute concurrently?
4、How many tasks may be queued pending execution?
5、 If a task has to be rejected because the system is overloaded,
which task should be selected as the victim, and how should the application be notified?
6、 What actions should be taken before or after executing a task?
5、Executor之线程池
在Executors 中提供了很多静态工厂方法来创建线程池,包括:
newCachedThreadPool() Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available. newFixedThreadPool(int nThreads) Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue. newScheduledThreadPool(int corePoolSize) Creates a thread pool that can schedule commands to run after a given delay, or to execute periodically. newSingleThreadExecutor() Creates an Executor that uses a single worker thread operating off an unbounded queue. newSingleThreadScheduledExecutor() Creates a single-threaded executor that can schedule commands to run after a given delay, or to execute periodically. newWorkStealingPool() Creates a work-stealing thread pool using all available processors as its target parallelism level. newWorkStealingPool(int parallelism) Creates a thread pool that maintains enough threads to support the given parallelism level, and may use multiple queues to reduce contention.
其中,除了newWorkStealingPool外,都提供了用ThreadFactory来指定线程创建方式的重载。
因为Executor以异步的方式来执行任务,在任何时刻,任务的状态都是不确定的。
为了解决执行服务的生命周期问题,在Executor接口上扩展出了ExecutorService接口。
ExecutorService的生命周期有三种状态:运行、关闭和已终止。
shutdown() 方法不接受新的任务,并等待已经提交的任务执行完毕,然后再关闭。
shutdownNow() 简单粗暴,尝试取消正在执行的任务,不再启动已提交但未开始的任务。
/** * 支持关闭的web服务器 */ class LifecycleWebServer { private final ExecutorService exec = ...; //采取不同的线程池 public void start() throws IOException { ServerSocket socket = new ServerSocket(80); while (!exec.isShutdown()) { try { final Socket conn = socket.accept(); //通过使用线程池来执行任务 exec.execute(new Runnable() { public void run() { handleRequest(conn); } }); } catch (RejectedExecutionException e) { if (!exec.isShutdown()) log("task submission rejected", e); } } } public void stop() { exec.shutdown(); } void handleRequest(Socket connection) { Request req = readRequest(connection); if (isShutdownRequest(req)) stop(); else dispatchRequest(req); } }
6、延迟任务与周期任务
Timer类负责管理延迟任务,但是它有一些缺陷,尽量用ScheduledThreadPoolExecutor来替代它。
1、Timer只使用了单线程来执行任务,如果一个任务过长,后面的延时就不准确了。
2、如果TimerTask抛出未检查的异常,Timer线程不会捕获异常,也不会恢复线程执行。
因此以调度但尚未执行的任务不会执行,也不能提交新的任务。(线程泄漏)
构造自己的延时任务时,可采用DelayQueue。
7、找出任务的并行性
采用一个页面渲染的例子来解释:
页面渲染就是解析HTML文件,并显示。其中有的是css样式,有的是静态文本,
而有的是资源链接,这些资源还要继续下载。
1、串行的页面渲染
public class SingleThreadRenderer { void renderPage(CharSequence source) { renderText(source); //先绘制文本
List<ImageData> imageData = new ArrayList<ImageData>(); for (ImageInfo imageInfo : scanForImageInfo(source)) //下载图片 imageData.add(imageInfo.downloadImage()); for (ImageData data : imageData) //绘制图片 renderImage(data); }
}
2、采用多线程,单独开一个线程来下载图片
但是,有一个问题。使用Runnable来表示任务没法获取任务执行的返回值啊。。
使用Future!
Futrue表示一个任务的生命周期,并提供了相应的方法来判断是否已经完成或取消。
ExecutorService中所有的submit方法都返回一个Future。
public class FutureRenderer { private final ExecutorService executor = ...; void renderPage(CharSequence source) { final List<ImageInfo> imageInfos = scanForImageInfo(source); Callable<List<ImageData>> task = new Callable<List<ImageData>>() { public List<ImageData> call() { List<ImageData> result = new ArrayList<ImageData>(); for (ImageInfo imageInfo : imageInfos) result.add(imageInfo.downloadImage()); return result; } }; Future<List<ImageData>> future = executor.submit(task); renderText(source); try { List<ImageData> imageData = future.get(); //阻塞直到任务完成 for (ImageData data : imageData) renderImage(data); } catch (InterruptedException e) { // Re-assert the thread's interrupted status Thread.currentThread().interrupt(); // We don't need the result, so cancel the task too 收到中断请求时,就取消future的任务,并恢复中断 future.cancel(true); } catch (ExecutionException e) { throw launderThrowable(e.getCause()); } } }
3、下载完一张图片就渲染一张
需要实现下载一张就渲染一张,就要考虑一些问题:
是将下载图片提交为一个任务还是一张图片一个任务?
怎么获取下载好的图片(任务结果)?要记住Future的get是阻塞的哦。
使用CompletionService!
CompletionService:
A service that decouples the production of new asynchronous tasks from the consumption of the results of completed tasks. Producers submit tasks for execution. Consumers take completed tasks and process their results in the order they complete. A CompletionService can for example be used to manage asynchronous I/O, in which tasks that perform reads are submitted in one part of a program or system,
and then acted upon in a different part of the program when the reads complete, possibly in a different order than they were requested.
CompletionService融合了Executor和BlockingQueue的功能。它使用Executor执行任务,然后将完成的任务按照完成顺序保存在一个BlockingQueue中。
public class Renderer { private final ExecutorService executor; Renderer(ExecutorService executor) { this.executor = executor; } void renderPage(CharSequence source) { final List<ImageInfo> info = scanForImageInfo(source); CompletionService<ImageData> completionService = new ExecutorCompletionService<ImageData>(executor); for (final ImageInfo imageInfo : info) completionService.submit(new Callable<ImageData>() { //添加任务 public ImageData call() { return imageInfo.downloadImage(); } }); renderText(source); try { for (int t = 0, n = info.size(); t < n; t++) { Future<ImageData> f = completionService.take(); //实时获取,没有完成的则阻塞 ImageData imageData = f.get(); renderImage(imageData); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (ExecutionException e) { throw launderThrowable(e.getCause()); } } }
8、为任务设置时限,超时则取消
有时候,如果在给定时间内任务没有给出结果,我们就认为它没有什么价值了。
这时候,就可以取消它。以避免浪费计算资源。
可以使用Future,通过限时的get方法,超时时,get方法会抛出TimeOutException,
此时我们就可以将它关闭。
/** * 在指定时间内获取广告信息 */ Page renderPageWithAd() throws InterruptedException { long endNanos = System.nanoTime() + TIME_BUDGET; Future<Ad> f = exec.submit(new FetchAdTask()); // Render the page while waiting for the ad Page page = renderPageBody(); Ad ad; try { // Only wait for the remaining time budget long timeLeft = endNanos - System.nanoTime(); ad = f.get(timeLeft, NANOSECONDS); } catch (ExecutionException e) { ad = DEFAULT_AD; } catch (TimeoutException e) { ad = DEFAULT_AD; //超时则取消广告加载,并显示默认广告 f.cancel(true); } page.setAd(ad); return page; }
还有一种实现方式,就是,使用支持限时的invokeAll方法(该方法会阻塞,直到方法都完成或时限到达)。
该方法会返回一组Future对象。
在时限到达时,去轮询Future对象,如果时限到达时还未完成,就结束它。
/**
* 在预定时间内请求旅游报价
*/
private class QuoteTask implements Callable<TravelQuote> { private final TravelCompany company; private final TravelInfo travelInfo; ... public TravelQuote call() throws Exception { return company.solicitQuote(travelInfo); } } 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; }

浙公网安备 33010602011771号