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

一、在线程中执行任务

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

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并发编程实战 第16章 Java内存模型

    什么是内存模型 JMM(Java内存模型)规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对其他线程可见. JMM为程序中所有的操作定义了一个偏序关系,称为Happens-Be ...

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

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

  4. 【java并发编程实战】第一章笔记

    1.线程安全的定义 当多个线程访问某个类时,不管允许环境采用何种调度方式或者这些线程如何交替执行,这个类都能表现出正确的行为 如果一个类既不包含任何域,也不包含任何对其他类中域的引用.则它一定是无状态 ...

  5. Java并发编程实战 第8章 线程池的使用

    合理的控制线程池的大小: 下面内容来自网络.不过跟作者说的一致.不想自己敲了.留个记录. 要想合理的配置线程池的大小,首先得分析任务的特性,可以从以下几个角度分析: 任务的性质:CPU密集型任务.IO ...

  6. JAVA并发编程实战---第三章:对象的共享(2)

    线程封闭 如果仅仅在单线程内访问数据,就不需要同步,这种技术被称为线程封闭,它是实现线程安全性的最简单的方式之一.当某个对象封闭在一个线程中时,这种方法将自动实现线程安全性,即使被封闭的对象本生不是线 ...

  7. java并发编程实战:第二章----线程安全性

    一个对象是否需要是线程安全的取决于它是否被多个线程访问. 当多个线程访问同一个可变状态量时如果没有使用正确的同步规则,就有可能出错.解决办法: 不在线程之间共享该变量 将状态变量修改为不可变的 在访问 ...

  8. Java并发编程实战 第15章 原子变量和非阻塞同步机制

    非阻塞的同步机制 简单的说,那就是又要实现同步,又不使用锁. 与基于锁的方案相比,非阻塞算法的实现要麻烦的多,但是它的可伸缩性和活跃性上拥有巨大的优势. 实现非阻塞算法的常见方法就是使用volatil ...

  9. Java并发编程实战 第10章 避免活跃性危险

    死锁 经典的死锁:哲学家进餐问题.5个哲学家 5个筷子 如果没有哲学家都占了一个筷子 互相等待筷子 陷入死锁 数据库设计系统中一般有死锁检测,通过在表示等待关系的有向图中搜索循环来实现. JVM没有死 ...

  10. Java并发编程实战 第5章 构建基础模块

    同步容器类 Vector和HashTable和Collections.synchronizedXXX 都是使用监视器模式实现的. 暂且不考虑性能问题,使用同步容器类要注意: 只能保证单个操作的同步. ...

随机推荐

  1. 真实赛车3,FERRARI之魂不买FERRARI 599 GTO可以解锁顶点系列。

    难点1,在仅有458 SPIDER的情况下,“TURBO BURST技巧混战”中 Mount Panorama速度快照,比较难.多重试十几次. 难点2,“TURBO BURST大满贯”中直道赛,用45 ...

  2. ruby里面的属性访问器

    和ios的@property一样 attr_accessor 表明是示例的getter和setter 下面的是rails的扩展,裸体class里面用,貌似会报错 cattr_accessor 表明是类 ...

  3. ddt数据驱动

    数据驱动原理 1.测试数据为多个字典的list类型 2.测试类前加修饰@ddt.ddt 3.case前加修饰@ddt.data() 4.运行后用例会自动加载成三个单独的用例 5.测试结果: Testi ...

  4. Mac安装appium 问题记录

    执行脚本报错:Xcode version [object Object] is not yet supported 原因:Xcode8以上的版本不支持Appium-1.5.3版本

  5. Train-Alypay-Cloud:mPaaS 移动开发平台培训(第一次)

    ylbtech-Train-Alypay-Cloud:mPaaS 移动开发平台培训(第一次) 1.返回顶部 1. 大家好! 欢迎大家参加蚂蚁金融云 即将在2018年1月17日到1月18日 在北京 环球 ...

  6. application/json 和 application/x-www-form-urlencoded的区别

    public static string HttpPost(string url, string body) { //ServicePointManager.ServerCertificateVali ...

  7. 关于C语言中%p和%X的思考

    说白了,(%A)仅仅代表以何种格式显示所要显示的数据,具体何种格式如下: %d 有符号10进制整数 %i 有符号10进制整数 %o 无符号8进制整数 %u 无符号10进制整数 %x 无符号的16进制数 ...

  8. Ubuntu12.04编译vlc-android详细流程

    作者:wainiwann 出处:http://www.cnblogs.com/wainiwann/ 本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则 ...

  9. my sql存储过程 基本使用

    --------------创建不带参数的存储过程----------------- DELIMITER;; drop PROCEDURE if EXISTS selectStudent; creat ...

  10. 【转】C# 调用 C++ 数据转换

    原文:https://www.cnblogs.com/82767136/articles/2517457.html 在合作开发时,C#时常需要调用C++DLL,当传递参数时时常遇到问题,尤其是传递和返 ...