您的位置:首页 > 编程语言 > Java开发

Java并行编程-第六章 任务执行-学习总结

2019-08-15 15:52 253 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/Rookie20190715/article/details/99622277

第六章 任务执行(Task Execution)

在线程中执行任务

最简单策略就是在单个线程中串行地执行任务。
主线程在接受连接与处理相关请求等操作之间不断交替运行。当服务器正在处理请求是时,新到来的连接必须等待直到请求处理完成,然后服务器将再次调用accept。

显示的为任务创建线程
通过为每个请求创建一个新的线程来提供服务,从而实现更高的响应性。
对于每个连接,主循环都将创建一个新的线程来处理请求,而不是在主循环内进行处理。

  • 任务处理过程中从主线程中分离出来,使得主循环能够更快的重新等待下一个到来的连接。这使得程序在完成前面的请求之前可以接受新的请求,从而提高响应性。
  • 任务可以并行处理,从而能同时服务多个请求。如果有多个处理器,或者任务由于某种原因被阻塞,例如等待I/O完成、
  • 任务处理代码必须是线程安全的,因为当有多个任务时会并发的调用这段代码。

无限制创建线程的不足
线程生命周期的开销非常高。
资源消耗。
稳定性。

Executor 框架

该框架能支持多种不同类型的任务执行策略。它提供了一种标准的方法将任务的提交过程与执行过程解耦开来,并用Runnable表示任务。Executor的实现还提供了对生命周期的支持,以及统计信息收集,应用管理机制和性能监视等机制。

线程池

线程池从字面含义,指一组同构工作线程的资源池。线程池与工作队列(work queue)密切相关,其中工作队列中保存了所有等待执行的任务。工作者线程的任务:从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。

优点:

  • 可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销。
  • 当请求到达的时候,工作线程已将存在,不用等待创建线程。
  • 适当调整线程池大小,可以创建足够多的线程以变使处理器保持子啊忙碌状态
  • 防止过多线程相互竞争资源而使应用程序耗尽内存或失败。

调用Executor是中的静态工厂方法之一来创建一个线程池:
newFixedThreadPool。newFIxedThreadPool将创建一个固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的最大数量,这是线程池的规模不再变化。
newCachedThreadPool。newCachedThreadPool将创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,而当需求增加时,则可以添加新的线程池,线程池的规模不存在任何限制。
newSIngleThreadExector。newSingleThreadExecutor是一单线程的Executor,它将创建单个工作线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。newSingleThreadExecutor能确保依照任务在队列中顺序来串行执行。
newScheduledThreadPool。newScheduledThreadPool创建了一个固定长度的线程池,而且以延迟或者定时的方式来执行任务,类似于Timer。

Executor的生命周期

ExecutorServce 的生命周期有三种状态:运行、关闭和已终止。ExecutorService在出事创建是处于运行状态。shutdow方法将执行平缓的关闭过程:不再接受新的任务,同时等待已提交的任务执行完成——包括那些还未开始执行的任务。shutdownNow方法将执行粗暴的关闭过程:它将尝试取消所有运行中的任务,并且不再启动尚未开始执行的任务。

延迟任务与周期任务

TImer类负责管理延迟任务(“在100ms后执行该任务”)以及周期任务(“每10ms执行依次该任务”)。
然而Timer支持给予绝对时间而不是相对时间的调度机制,因此任务的执行系统时钟变化很敏感,而ScheduledThreadPoolExecutor只支持基于相对时间的调度。可以通过ScheduledThreadPoolExecutor的构造函数或newScheduledThreadPool工厂方法来创建该类的对象。

找出可利用的并行性

携带结果的任务Callable 与 Future
Executor框架使用Runnable作为其基本任务的表现形式。Runnable是一种有很大局限的抽象,虽然run能写入到日志文件或者将结果放入某个共享的数据结构,但它不能返回一个值或抛出一个受检查的异常。
许多任务实际都是存在延迟的计算——执行数据库查询,从网络上获取资源,或者计算某个复杂的功能。对于这些任务,Callable是一种更好的抽象:它认为主入口点将返回一个值,并可能抛出一个异常。
Future表示一个任务的生命周期,并提供了相应的方法来判断是否已经完成或取消,以及获取任务的结果和取消任务等。
在Future规范中包含的隐含意义是:任务的生命周期只能前进,不能后退,就像ExecutorService的生命周期一样。当某个任务完成后,它就永远停留在“完成”的状态。
get方法的行为取决与任务的状态(尚未开始,正在运行,已完成)。

  • 任务已完成,get会立即返回或者抛出一个Exception。
  • 任务未完成,get将阻塞并直到任务完成。
  • 任务抛出异常,get将该异常封装成ExecutionException并重新抛出。并且可以通过getCause来获得被封装的初始异常。
  • 任务被取消,get将抛出CancellationException。
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;
}

单页地渲染页面元素

public class SingleThreadRenderer {
void renderPage(CharSequence source) {
renderText(source);
List<ImagerData> imageData = new ArrayList<ImageData>();
for (ImageInfo imageInfo : scanForImageInfo(source))
imageData.add(imageInfo.downloadImage());
for (ImageData data : imageData)
renderImage(data);
}
}

使用Future等待图像下载

public class FutureRenderer {
private fianl ExecutorService executor = ...;

void renderPage(CharSequence source) {
final List<ImageInfo> imageInfos = scanForImagerInfo(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.downloadImageA());
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 ) {
Thread.currentThread().interrupt();
future.cancel(ture);
} catch (ExecutionException e) {
throw lauderThrowable(e.getCause());
}
}
}

在异构任务并行中存在的局限

通过对异构任务进行并行化来获得重大的性能提升是很困难的。
只用当大量相互独立且同构的任务可以并发进行处理时,才能体现出将程序的工作负载分配到多个任务中带来的真正性能提升。

CompletionService: Executor 与 BlockingQueue
完成服务(CompletionService) 将Executor和BlockingQueue的功能融合在一起。可以寄将Callable任务提交给它来执行,然后使用类队列操作的take和poll等方法来获得已完成的结果,而这些结果会在完成时将被封装为Future。ExcutorCompletionService实现了COmpletionService,并将计算部分委托给一个Executor。
ExecutorCompletionService的实现非常简单。在构造函数中创建一个BlockingQueue来保存计算完成的结果。当计算完成时,调用Future-Task的done方法。当提交某个任务时,该任务将首先包装为一个QueueingFuture,这是FutureTask的一个子类,然后再改写子类的done方法,并将结果放入BlockingQueue中。如下面程序,take和poll方法委托给了BlockingQueue,这些方法会在得出结果之前阻塞。

由ExecutorCompletionService使用的QueueingFuture类

private class QueueingFuture<V> extends FutureTask<V> {
QueueingFuture(Callable<V> c) {
super(c);
}
QueueingFuture(Runnable t, V r) {
super(t, r);
}
protected void done() {
completionQueue.add(this);
}
}

为任务设置时限

例子:给出了限时Future.get的一种典型应用。在它生成的页面中包括响应用户请求的内容以及从广告服务器上获得的广告。它将获取广告的任务提交给一个Executor,然后计算剩余的文本页面内容,最后等待广告信息,直到超出指定的时间。如果get超时,那么将取消广告获取任务,并转而使用默认的广告信息。

Page renderPageWithAd() throws InterruptedException {
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, NANOSECONDS);
} catch (ExecutionException e) {
ad = DEFAULT_AD;
} catch (TimeoutException e) {
ad = DEFAULT_AD;
f.cancel(true);
}
page.setAd(ad);
return page;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: