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

一、在线程中执行任务

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

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. Redis原理(一)

    基础和应用 1.Redis是远程调用技术的首字母缩写. 2.Redis可以用来做什么? Redis可以用来做缓存. 分布式锁 3.Redis的应用举例 记录帖子的点赞数.评论数和点击数.(使用HASH ...

  2. 逆波兰法(计算器)程序<无括号版>

    涉及队列.栈的运用. Java中队列可以用: Queue<String> q = new LinkedList(); 来声明,其主要的方法有: poll(),peak(),offer(), ...

  3. python相关系数

    皮尔逊相关系数: 用于度量两个变量X和Y之间的相关(线性相关),其值介于-1与1之间. 几组的点集,以及各个点集中和之间的相关系数.我们可以发现相关系数反映的是变量之间的线性关系和相关性的方向(第一排 ...

  4. 【例题 7-6 UVA - 140】Bandwidth

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 暴力做就好. O(8!*26^2) [代码] /* 1.Shoud it use long long ? 2.Have you ev ...

  5. 【Codeforces Round #432 (Div. 2) B】Arpa and an exam about geometry

    [链接]h在这里写链接 [题意] 给你3个点A,B,C 问你能不能将纸绕着坐标轴上的一点旋转.使得A与B重合,B与C重合 [题解] 这3个点必须共圆. 则A,B,C不能为一条直线.否则无解. 共圆之后 ...

  6. java线程——详解Callable、Future和FutureTask

    回顾: 接上篇博客 java线程--三种创建线程的方式,这篇博客主要介绍第三种方式Callable和Future.比较继承Thread类和实现Runnable接口,接口更加灵活,使用更广泛.但这两种方 ...

  7. QSYS系统简介

    QSYS是SoPC Builder的新一代产品. QSYS系统集成工具自动生成互联逻辑,连接IP和子系统 QSYS的设计理念是提高设计抽象级,从而使机器自动生成底层代码. Altera的Avalon总 ...

  8. HttpClient请求发送的几种用法二:

    public class HttpClientHelper    {        private static readonly HttpClientHelper _instance = new H ...

  9. IIS服务器设置http自动跳转https方法

    很多站长在部署SSL证书后,网站实现https加密访问,但考虑到用户习惯了http访问,很多外链也是http访问形式,所以需要在IIS服务器配置http自动跳转https,避免用户通过http访问不到 ...

  10. pcb过孔盖油

    pcb的过孔应该盖油,,这样,两个距离比较紧的过孔就不会在焊接的时候短路了,尤其是手工焊接小件的时候.