第8章 线程池的使用

8.1 在任务与执行策略之间的隐性耦合

虽然Executor框架为制定和修改执行策略都提供了相当大的灵活性,但并非所有的任务都适用所有的执行策略。有些类型的任务需要明确地指明执行策略:

  • 依赖性任务
  • 使用线程封闭机制的任务
  • 对响应时间敏感的任务
  • 使用ThreadLocal的任务

只有当任务是同类型并且互相独立时,线程池才能达到最佳性能。

-8.1.1 线程饥饿死锁

在线程池中,如果任务依赖与其他任务,那么可能会产生死锁。给出个具体的例子:

同样的,当提交了有依赖性的Excecutor任务时,如果线程池不够大也会发生死锁。所以要清楚地知道可能会出现的线程“饥饿”死锁,因此需要在代码或配置文件中记录或设置线程池的大小

-8.1.2  运行时间较长的任务

限定任务等待资源时间,而不是无限制地等待,可以缓解执行时间较长任务的影响。

8.2 设置线程池的大小

8.3 配置ThreadPoolExecutor

ThreadPoolExecutor为一些Executor提供了基本的实现,是一个灵活的,稳定的线程池,允许进行各种定制。

-8.3.1 线程的创建与销毁

线程池的基本大小(Core Pool Size), 最大大小(Maximum Pool Size)以及存活时间等因素共同负责线程的创建与销毁。基本大小是没有任务执行时线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。最大大小表示可同时活动的线程数量上限。如果某个线程的空闲时间超过了存活时间,那么将被标记为可回收的,并且当线程池的当前大小超过了基本大小时,这个线程将被终止。

newFixedThreadPool工厂方法将线程池的基本大小和最大大小设置为参数中指定的值,而且创建的线程池不会超时。newCachedThreadPool工厂方法将线程池的最大大小设置为Integer.MAX_VALUE,而将基本大小设置为0,并将超时设置为1分钟,这种方法创建出来的线程池可以被无限扩展,并且当需求降低时会自动收缩。

-8.3.2 管理任务队列

在有限的线程池中会限制可并发执行的任务数量。ThreadPoolExecutor允许提供一个BlockingQueue来保存等待执行的任务。基本的任务安排方法有3种:无界队列,有界队列和同步移交。

newFixedThreadPool和newSingleThreadExecutor在默认情况下将使用一个无界的LinkedBlockingDeque。如果所有工作者都处于忙碌状态,那么任务将在队列中等候。如果任务持续快速地到达,并且超过了线程池处理它们的速度,那么队列将无限增加。一种更稳妥的做法是使用有界队列,如ArrayBlockingQueue,有界的LinkedBlockingDeque,PriorityBlockingQueue。有界队列有助于避免资源耗尽的情况,但是当队列填满后,新的任务该怎么办呢?在使用有界的工作队列时,队列的大小与线程池的大小必须一起调节。对于非常大的或者无界的线程池,可以通过使用SynchronousQueue来避免任务排队,以直接将任务从生产者移交给工作这线程。SynchronousQueue不是一个真正的队列,而是一种在线程之间进行移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接受这个元素。如果没有线程正在等待,并且线程池的当前大小小于最大值,那么ThreadPoolExecutor将创建一个新的线程,否则根据饱和策略,这个任务将被拒绝。

-8.3.3 饱和策略

当有界队列被填满后,饱和策略开始发挥作用。ThreadPoolExecutor的饱和策略可以通过调用setRejectedExecutionHandler来修改,JDK提供了几种不同的RejectedExecutionHandler实现,每种实现都包含有不同的饱和策略。“中止(Abort)”是默认的策略,该策略将抛出未检查的DiscardExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。当新提交的任务无法保存到队列中等待执行时,“抛弃(Discard)”策略会悄悄抛弃该任务。“调用者运行(Caller-Runs)”策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者。

当工作队列被填满后,没有预定义的饱和策略来阻塞execute,通过使用信号量(Semaphore)来限制任务的到达率,就可以实现这个功能:

写到这里,才发现有件事可能自己弄反了,关于提交工作任务是哪一步的问题?从上面的理解来看,提交新工作的那一步相对应的代码是 exec .execute(new Runnable()), 所以工作队列被填满后需要阻塞execute

-8.3.4 线程工厂

每当线程需要创建一个线程时,都是通过线程工厂方法来完成的,默认创建一个新的,非守护的线程,并不包含特殊的配置信息。在许多情况下需要定制线程的工厂方法。

在MyAppThread中还可以定制其他行为,如:

如果在应用程序中需要利用安全策略来控制对某些代码库的访问权限,那么可以通过Executor中的privilegedThreadFactory工厂来定制自己的线程工厂。通过这种方式创建出来的线程,将与创建privilegedThreadFactory的线程拥有相同的访问权限,AccesssControlContext和contextClassLoader。

-8.3.5 在调用构造函数后再定制ThreadPoolExecutor

可以通过设置函数Setter来修改大多数传递给它的构造函数的参数:

8.4 扩展ThreadPoolExecutor

提供了可以在子类中改写的方法:beforeExecute,afterExecute和terminated,来个例子:

8.5 递归算法的并行化

如果循环中的迭代操作都是独立的,并且不需要等待所有的迭代操作都完成再继续执行,那么就可以使用Executor将串行循环转化为并行循环。在一些递归设计中同样可以采用循环并行的方法。下面实际来讲解一个例子:谜题框架,这些谜题都需要找出一系列的操作从初始状态转换到目标状态。

我们将谜题定义为:包含一个初始位置,一个目标位置,以及用于判断是否有效移动的规则集。规则集包含两部分:计算从指定位置开始的所有合法移动,以及每次移动的结果位置。

可以以并行方式来计算下一步移动以及目标条件,因为计算某次移动的过程很大程度上与计算其他移动的过程是相互独立的。

程序清单8-17的ValueLatch中使用CountDownLatch来实现所需的闭锁行为,并且使用锁定机制来确保解答只会被设置一次:

没个任务首先会查询solution闭锁,找到一个解答就停止。而在此之前,主线程需要等待。第一个找到解答的线程还会关闭Executor,从而阻止接受新的任务。

如果不存在解答,那么ConcurrentPuzzleSolver就不能很好地处理这种情况:如果已经遍历了所有的移动和位置都没找到解答,那么在getSolution调用中将会永远等待下去。要结束并发程序,其中一种方法是:记录活动任务的数量,当该值为零时将解答设为null,如:

上面的程序让我想到了设计模式中的装饰器模式,这样第8章就看完喽!第九章是关于图形界面的,java里有关图形的我是一律pass掉。终于进入这本书的第三部分了,希望可以早日看完。

《java并发编程实战》读书笔记7--线程池的使用的更多相关文章

  1. Java并发编程实战 读书笔记(一)

    最近在看多线程经典书籍Java并发变成实战,很多概念有疑惑,虽然工作中很少用到多线程,但觉得还是自己太弱了.加油.记一些随笔.下面简单介绍一下线程. 一  线程与进程   进程与线程的解释   个人觉 ...

  2. 《Java并发编程实战》第八章 线程池的使用 读书笔记

    一.在任务与运行策略之间的隐性解耦 有些类型的任务须要明白地指定运行策略,包含: . 依赖性任务.依赖关系对运行策略造成约束.须要注意活跃性问题. 要求线程池足够大,确保任务都能放入. . 使用线程封 ...

  3. Java并发编程实战 读书笔记(二)

    关于发布和逸出 并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了.这是危及到线程安全的,因为其他线程有可能通过这个 ...

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

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

  5. java并发编程实战:第八章----线程池的使用

    一.在任务和执行策略之间隐性耦合 Executor框架将任务的提交和它的执行策略解耦开来.虽然Executor框架为制定和修改执行策略提供了相当大的灵活性,但并非所有的任务都能适用所有的执行策略. 依 ...

  6. Java并发编程(您不知道的线程池操作)

    Java并发编程(您不知道的线程池操作) 这几篇博客,一直在谈线程,设想一下这个场景,如果并发的线程很多,然而每个线程如果执行的时间很多的话,这样的话,就会大量的降低系统的效率.这时候就可以采用线程池 ...

  7. Java并发编程(您不知道的线程池操作), 最受欢迎的 8 位 Java 大师,Java并发包中的同步队列SynchronousQueue实现原理

    Java_并发编程培训 java并发程序设计教程 JUC Exchanger 一.概述 Exchanger 可以在对中对元素进行配对和交换的线程的同步点.每个线程将条目上的某个方法呈现给 exchan ...

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

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

  9. Java多线程编程实战读书笔记(一)

    多线程的基础概念本人在学习多线程的时候发现一本书——java多线程编程实战指南.整理了一下书中的概念制作成了思维导图的形式.按照书中的章节整理,并添加一些个人的理解.

  10. 读书笔记-----Java并发编程实战(一)线程安全性

    线程安全类:在线程安全类中封装了必要的同步机制,客户端无须进一步采取同步措施 示例:一个无状态的Servlet @ThreadSafe public class StatelessFactorizer ...

随机推荐

  1. 《Java程序设计》第五周学习总结 20165218 2017-2018-2

    <Java程序设计>第五周学习总结 20165218 2017-2018-2 教材学习内容总结 第7章 内部类与异常类 内部类 在类中定义另一个类,外嵌类和内部类 内部类可以用外嵌类中的成 ...

  2. YBT 5.3 数位动态规划

    记忆化搜索的专题 题解在代码中 Amount of Degrees[loj 10163] /* 此题可以转换成将10进制转成b进制后有k个1其他都为0的个数 所以用记忆化dfs dp[pos][sum ...

  3. 【状压DP】【P2831】【NOIP2016D2T3】愤怒的小鸟

    传送门 Description Kiana 最近沉迷于一款神奇的游戏无法自拔. 简单来说,这款游戏是在一个平面上进行的. 有一架弹弓位于 $(0,0)$ 处,每次 Kiana 可以用它向第一象限发射一 ...

  4. Nginx反向代理两个tomcat服务器

    第一步,在Linux上安装两个tomcat,修改好端口号后,启动起来. 第二步,配置本地的DNS解析,即修改host文件: 第三步,配置Nginx配置文件 反向代理的配置虚拟主机配置差不多也要配置虚拟 ...

  5. some interesting words

    No one gets rich betting against the market. Never bet against the Fed. Bulls make money, bears make ...

  6. OpecnCV训练分类器详细整理

    本文主要是对下面网址博客中内容的实例操作: http://blog.csdn.net/byxdaz/article/details/4907211 在上述博客中,详细的讲述了OpenCV训练分类器的做 ...

  7. ACM1004 Let the balloons fly

    These code is for the problem "Let the balloons Fly" in ACM 1004 which need deal with stri ...

  8. POJ 1661 DP

    Help Jimmy Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 11071   Accepted: 3607 Descr ...

  9. 第一篇 关于Android Studio的快捷键

    公司最近要培训Android的课程,但是发现现在官方网站上已经不提供了Eclipse ADT的下载了,都变成了Android Studio,可能是悲催了! 对于很多Eclipse转过来的同学,不适应的 ...

  10. 动态规划小结 - 一维动态规划 - 时间复杂度 O(n),题 [LeetCode] Jump Game,Decode Ways

    引言 一维动态规划根据转移方程,复杂度一般有两种情况. func(i) 只和 func(i-1)有关,时间复杂度是O(n),这种情况下空间复杂度往往可以优化为O(1) func(i) 和 func(1 ...