任务:通常是一些抽象的且离散的工作单元。大多数并发应用程序都是围绕"任务执行"来构造的,把程序的工作分给多个任务,可以简化程序的组织结构便于维护

一、在线程中执行任务

任务的独立性:任务并不依赖于其他任务的状态,结果和边缘效应。独立的任务可以实现并行执行

1、串行的执行任务

所有的任务放在单个线程中串行执行,程序简单,安全性高,不涉及同步等情况,缺点也显而易见,无法提高吞吐量和响应速度,适合任务数量很少并且执行时间很长时,或者只为单个用户使用,并且该用户每次只发出一个请求。

2、显示的创建线程

为每一个请求创建一个线程,将任务的处理从主线程中分离出来,多个任务可以并行处理,充分利用了系统资源,提高吞吐量和相应速度,要求处理代码必须是线程安全的

3、无限创建线程的不足

线程生命周期的开销非常高;太多线程会消耗系统资源,空闲线程的内存空间占用,大量线程竞争CPU时产生其他性能开销;稳定性:破坏这些限制底层操作系统对线程的限制很可能抛出OutOfMemoryError异常

总结:在一定范围内,增加线程有助于提高吞吐量,但是再多就可能导致性能下降。

二、Executor框架

任务是一组逻辑单元,而线程是使任务异步执行的机制

  • Executor简化了线程的管理工作,并且java.util.concurrent提供了一种灵活的线程池作为Executor框架的一部分。
  • Executor基于生产者—消费者设计模式,提交任务的操作单元相当于生产者(生成待完成的工作单元),执行任务的线程相当于消费者(执行完这些工作单元)
  • 将提交过程和执行过程解耦,用Runnable表示执行任务

1、基于Executor的web服务器

 1 public class ThreadPerTaskWebServer {
2 private static final int NTHREADS = 100;
3 /**
4 * 创建固定线程数量的线程池
5 */
6 private static final Executor exec =
7 Executors.newFixedThreadPool(NTHREADS);
8 public static void main(String[] args) throws IOException {
9 ServerSocket server = new ServerSocket(80);
10 boolean listening = true;
11 while (listening){
12 final Socket connection = server.accept(); //阻塞等待客户端连接请求
13 Runnable task = new Runnable() {
14 @Override
15 public void run() {
16 handlerRequest(connection);
17 }
18 };
19 exec.execute(task);
20 }
21 server.close();
22 }
23 ...
24 }

Executor创建了含有100个线程的线程池来处理任务

若想更改任务的处理方式,只需要使用不用的Executor实现

2、执行策略

  • 根据可用的资源和对服务质量的要求制定合理的执行策略
  • 将任务的提交与任务的执行解耦,有助于在部署阶段选择与硬件最匹配的执行策略

3、线程池:管理一组同构工作线程的资源池。

线程池vs工作队列:工作者线程来自线程池,从工作队列获取任务,执行完毕回到线程池

优点:不仅可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销,另外一个好处就是当请求到达时,工作线程通常已经存在,因此不会由于等待线程创建而延迟任务的执行

  • newFixedThreadPool:固定长度的线程池,即线程池的规模有上限。
  • newCachedThreadPool:可缓存的线程池,如果线程池的当前规模超过了处理需求时,将回收空闲的线程,而当需求增加时,则可以添加新的线程,注意线程池的规模不存在任何限制。
  • newSingleThreadExecutor:单线程的Executor,通过创建单个工作者线程来串行的执行任务,如果此线程异常结束,Executor会创建另一个线程来代替。注意此模式能确保依照任务在队列中的顺序来串行执行(例如FIFO、LIFO、优先级)。
  • newScheduledThreadPool:创建固定长度的线程池,而且以延迟或者定时的方式来执行任务。

4、Executor的生命周期

newXXXThreadPool都是返回的ExecutorService

ExecutorService的生命周期主要有三种状态:运行、关闭和已终止。

为了解决执行服务的生命周期问题,ExecutorService扩展了Executor接口,添加了管理生命周期的方法。

  • shutdown:关闭线程池,不再接受新任务,等待已经提交的任务完成
  • shutdownNow:强制立即关闭线程池,返回的等待执行的任务列表,执行中的任务抛出中断异常
  • isShutdown:是否处于正在关闭状态
  • isTerminated:是否结束
  • awaitTerminated:阻塞等待关闭完成

5、延迟任务和周期任务

通过ScheduledThreadPoolExecutor来代替Timer,TimerTask。

  • Timer基于绝对时间,ScheduledThreadPoolExecutor基于相对时间。
  • Timer执行所有定时任务只能创建一个线程,若某个任务执行时间过长,容易破坏其他TimerTask的定时精确性。
  • Timer不捕获异常,Timetask抛出未检查的异常会终止定时器线程,已经调度但未执行的TimerTask将不会再执行,新的任务也不会被调度,出现"线程泄漏"
 1 public class OutOfTime {
2 public static void main(String[] args) throws InterruptedException {
3 Timer timer = new Timer();
4 timer.schedule(new ThrowTask(), 1); //第一个任务抛出异常
5 Thread.sleep(1000);
6 timer.schedule(new ThrowTask(), 1); //第二个任务将不能再执行, 并抛出异常Timer already cancelled.
7 Thread.sleep(5000);
8 System.out.println("end.");
9 }
10
11 static class ThrowTask extends TimerTask{
12
13 @Override
14 public void run() {
15 throw new RuntimeException("test timer's error behaviour");
16 }
17 }
18 }

三、找出可利用的并行性

1、携带结果的任务Callable与Future

  Runnable的缺陷:不能返回一个值,或抛出一个异常

  Callable和Runnable都描述抽象的计算任务,Callable可以返回一个值,并可以抛出一个异常

Executor执行任务的4个生命周期:创建,提交,开始,完成。Executor框架中,可以取消已提交但未开始执行的任务,对于已经开始执行的任务,只能当他们能响应中断时,才能取消,取消已经完成的任务不会有影响。

Future表示了一个任务的生命周期,提供了相应的方法判断是否完成或被取消以及获取执行结果

  • get方法:若任务完成,返回结果或抛出ExecutionException;若任务取消,抛出CancellationException;若任务没完成,阻塞等待结果
  • ExecutorService的submit方法提交一个Callable任务,并返回一个Future来判断执行状态并获取执行结果
  • 安全发布过程:将任务从提交线程穿个执行线程,结果从计算线程到调用get方法的线程

2、异构任务并行化中存在的局限:当异构任务之间的执行效率悬殊很大时,对于整体的性能提升来看并不是很有效。

3、完成服务CompletionService(Executor+BlockingQueue)

  使用BlockingQueue保存计算结果(Future),使用take和poll获取,计算部分同样委托给Executor

4、为任务设定时限:如果超出期望执行时间,将不要其结果

小结:通过围绕任务执行来设计应用程序,可以简化开发过程,并有助于实现并发。Executor框架将任务提交与执行策略解耦开来,同时还支持多种不同类型的执行策略。当需要创建线程来执行任务时,可以考虑使用Executor。要想在将应用程序分解为不同的任务时获得最大的好处,必须定义清晰的任务边界。某些应用程序中存在着比较明显的任务边界,而在其他一些程序中则需要进一步分析才能揭示出粒度更细的并行性。

方法小结

Future的get、cancel、isCancelled、isDone方法

get:在任务完成前一直阻塞。会抛出三种异常:CancellationException - 如果计算被取消、ExecutionException - 如果计算抛出异常、InterruptedException - 如果当前的线程在等待时被中断。

get(long timeout, TimeUnit unit):在超时之前且任务未完成则一直阻塞。除抛出以上三种异常

cancel(boolean mayInterruptIfRunning):试图取消对此任务的执行。如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败。当调用cancel时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,则mayInterruptIfRunning参数决定了是否调用运行任务的线程的interrupt操作。

isCancelled:如果在任务正常完成前将其取消,则返回true

isDone:正常终止、异常或取消而完成,在所有这些情况中,此方法都将返回 true 
ExecutorService的submit、invokeAll、invokeAny方法

ExecutorService的有三个重载的submit方法:

1、 可以接收Runnable或Callable类型的任务,返回Future<?>类型的Future的get返回null。 
2、 这三个方法都将提交的任务转换成了Future的实现类FutureTask实例,并作为submit的返回实例。 
3、 另外调用这三个方法不会阻塞,不像invokeAll那样要等到所有任务完成后才返回,与不像invokeAny那样要等到有一个任务完成后才返回Future。 
4、 这个三方法会调用Executor的execute来完成,因为Executor的execute会抛出RejectedExecutionException - 如果不能接受执行此任务、NullPointerException - 如果命令为 null这两个运行进异常,所以这三个方法也会抛出这两个异常。

T invokeAny(Collection<Callable<T>> tasks): 
1、 只要某个任务已成功完成(也就是未抛出异常,这与任务完成概念不一样:任务完成是指定Future的isDone返回true,有可能是抛出异常后进行完成状态),才返回这个结果。一旦正常或异常返回后,则取消尚未完成的任务(即任务所运行的线程处理中断状态,一旦在它上面出现可中断阻塞的方法调用,则会抛出中断异常)。 
2、 此方法会阻塞到有一个任务完成为止(正常完成或异常退出)。 
3、 也是调用Executor的execute来完成 
4、 调用get不会阻塞

invokeAny(Collection<Callable<T>> tasks, long timeout, TimeUnit unit): 
1、 只要在给定的超时期满前某个任务已成功完成(也就是invokeAny方法不能抛出异常,包括Future.get所抛的异常),则返回其结果。一旦正常或异常返回后,则取消尚未完成的任务。 
2、 此方法会阻塞到有一个任务完成为止(正常完成或异常退出)。 
3、 也是调用Executor的execute来完成 
4、 调用get不会阻塞

List<Future<T>> invokeAll(Collection<Callable<T>> tasks): 
1、 只有当所有任务完成时,才返回保持任务状态和结果的 Future 列表。返回列表的所有元素的 Future.isDone() 为 true。注意,可以正常地或通过抛出异常来已完成任务。 
2、 此方法会阻塞到所有任务完成为止(正常完成或异常退出)。 
3、 也是调用Executor的execute来完成,如果任务执行过程中抛出了其他异常,则方法会异常退出,且取消所有其他还未执行完成的任务。 
4、 返回的列表中的Future都是已经完成的任务,get时不会再阻塞

invokeAll(Collection<Callable<T>> tasks, long timeout, TimeUnit unit): 
1、 当所有任务完成或超时期满时(无论哪个首先发生),返回保持任务状态和结果的 Future 列表(如果是超时返回的列表,则列表中的会包括这些还未执行完的任务,使用get获取结果时可能会抛出CancellationException异常)。返回列表的所有元素的 Future.isDone() 为 true。一旦返回后,即取消尚未完成的任务。注意,可以正常地或通过抛出异常来完成任务。 
2、 此方法会阻塞到所有任务完成为止(正常完成或异常退出或超时)。 
3、 也是调用Executor的execute来完成,如果任务执行过程中抛出了其他异常,则方法会异常退出,且取消所有其他还未执行完成的任务。 
4、 返回的列表中的Future中会有因超时执行任务时异常而未执行完的任务,get时会抛出CancellationException或ExecutionException,当然所有的Future的get也不会阻塞。

第六章:任务执行——Java并发编程实战的更多相关文章

  1. Java并发编程实战---第六章:任务执行

    废话开篇 今天开始学习Java并发编程实战,很多大牛都推荐,所以为了能在并发编程的道路上留下点书本上的知识,所以也就有了这篇博文.今天主要学习的是任务执行章节,主要讲了任务执行定义.Executor. ...

  2. 《Java并发编程实战》学习笔记 任务执行和取消关闭

    查看豆瓣读书 第六章 任务执行 大多数并发应用程序是围绕执行任务进行管理的.设计任务时,要为任务设计一个清晰的任务边界,并配合一个明确的任务执行策略.任务最好是独立的,因为这会提高并发度.大多数服务器 ...

  3. 那些年读过的书《Java并发编程实战》和《Java并发编程的艺术》三、任务执行框架—Executor框架小结

    <Java并发编程实战>和<Java并发编程的艺术>           Executor框架小结 1.在线程中如何执行任务 (1)任务执行目标: 在正常负载情况下,服务器应用 ...

  4. 《java并发编程实战》笔记

    <java并发编程实战>这本书配合并发编程网中的并发系列文章一起看,效果会好很多. 并发系列的文章链接为:  Java并发性和多线程介绍目录 建议: <java并发编程实战>第 ...

  5. 【Java并发编程实战】-----“J.U.C”:ReentrantReadWriteLock

    ReentrantLock实现了标准的互斥操作,也就是说在某一时刻只有有一个线程持有锁.ReentrantLock采用这种独占的保守锁直接,在一定程度上减低了吞吐量.在这种情况下任何的"读/ ...

  6. 《Java并发编程实战》/童云兰译【PDF】下载

    <Java并发编程实战>/童云兰译[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062521 内容简介 本书深入浅出地介绍了Jav ...

  7. Java并发编程实战 02Java如何解决可见性和有序性问题

    摘要 在上一篇文章当中,讲到了CPU缓存导致可见性.线程切换导致了原子性.编译优化导致了有序性问题.那么这篇文章就先解决其中的可见性和有序性问题,引出了今天的主角:Java内存模型(面试并发的时候会经 ...

  8. Java并发编程实战 03互斥锁 解决原子性问题

    文章系列 Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题 摘要 在上一篇文章02Java如何解决可见性和有序性问题当中,我们解决了可见性和 ...

  9. Java并发编程实战 04死锁了怎么办?

    Java并发编程文章系列 Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题 Java并发编程实战 03互斥锁 解决原子性问题 前提 在第三篇 ...

随机推荐

  1. 在手机中预置联系人/Service Number

    代码分为两部分: Part One 将预置的联系人插入到数据库中: Part Two 保证预置联系人仅仅读,无法被编辑删除(在三个地方屏蔽对预置联系人进行编辑处理:联系人详情界面.联系人多选界面.新建 ...

  2. 转linux文件的读写

    转自 http://www.open-open.com/lib/view/open1474356438277.html 缓存 缓存是用来减少高速设备访问低速设备所需平均时间的组件,文件读写涉及到计算机 ...

  3. 使用@Order调整配置类加载顺序

    转自:https://blog.csdn.net/qq_15037231/article/details/78158553 4.1 @Order Spring 4.2 利用@Order控制配置类的加载 ...

  4. dbms_stats

    dbms_stats全部的功能包例如以下: GATHER_INDEX_STATS:分析索引信息 GATHER_TABLE_STATS:分析表信息,当cascade为true时,分析表.列(索引)信息 ...

  5. 微信支付v2开发(8) 维权通知

    本文介绍微信支付中如何获得维权通知. 一.维权通知URL 在 微信支付开发(1) 微信支付URL配置 已提到,维权通知URL为 http://www.doucube.com/wxpay/rights. ...

  6. ES6的基础知识总结

    一. ES6 ES6中定义变量使用 let/const let 使用let定义的变量不能进行"变量提升" 同一个作用域中,let不能重复定义相同的变量名 使用var在全局作用域中定 ...

  7. POJ 3669 Meteor Shower BFS 水~

    http://poj.org/problem?id=3669 题目大意: 一个人从(0,0)出发,这个地方会落下陨石,当陨石落在(x,y)时,会把(x,y)这个地方和相邻的的四个地方破坏掉,求该人到达 ...

  8. 【BZOJ 4310】跳蚤

    [链接]h在这里写链接 [题意]     给你一个字符串;     让你把它分割成最多k个部分.         然后求出每个部分的字符串里面子串的字典序最大的那一个子串.         然后在这k ...

  9. 全端project师必备技能汇总

    首先,看一张前端知识结构图:  (原文: ithomer) 图片的形式具有诸多的不便.缺失源图的我们.无法为此图贡献些什么,随着时间的迁移,也许有些技术点会发生改变.所以有了这个GitHub项目.我们 ...

  10. chrome-extensions -- copytables. verygood

    https://www.crx4chrome.com/extensions/ekdpkppgmlalfkphpibadldikjimijon/,通过设置快捷键,一般是拷贝多行