第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. 【图论】tarjan的离线LCA算法

    百度百科 Definition&Solution 对于求树上\(u\)和\(v\)两点的LCA,使用在线倍增可以做到\(O(nlogn)\)的复杂度.在NOIP这种毒瘤卡常比赛中,为了代码的效 ...

  2. 微信小程序将view动态填满全屏

    一.在app.js利用官方方法获取设备信息,将获取到的screenHeight.windowHeight度量单位统一由rpx换算为px 注:官方文档给出 [rpx换算px (屏幕宽度/750)  ][ ...

  3. php 获取周几

    date("l"); //date就可以获取英文的星期比如Sunday date("w"); //这个可以获取数字星期比如123,注意0是星期日 获取中文星期几 ...

  4. c# 定时执行任务

    在Global.asax文件中加上 void Application_Start(object sender, EventArgs e) { // Code that runs on applicat ...

  5. 挖一挖unsigned int和补码

    文章要讨论的是两部分: 1. 原码,反码和补码. 2. short, unsigned short, int, unsigned int, long, unsigned long的表示及转换 1. 原 ...

  6. Hadoop 介绍

    1.Hadoop简介 Hadoop[hædu:p]实现了一个分布式文件系统(Hadoop Distributed File System),简称HDFS.HDFS有高容错性的特点,并且设计用来部署在低 ...

  7. linux包安装,解压,压缩,包管理,环境变量

    linux 包安装,解压,压缩,包管理 centoscentos上有系统包管理器yum yum的配置一般有两种方式,一种是直接配置/etc目录下的yum.conf文件,另外一种是在/etc/yum.r ...

  8. 【JAVA】Pattern和Matcher

    ZZ: Java正则表达式:Pattern类和Matcher类 一.捕获组的概念 捕获组可以通过从左到右计算其开括号来编号,编号是从1 开始的.例如,在表达式 ((A)(B(C)))中,存在四个这样的 ...

  9. hdu 4408 Minimum Spanning Tree

    Problem Description XXX is very interested in algorithm. After learning the Prim algorithm and Krusk ...

  10. [洛谷P1707] 刷题比赛

    洛谷题目连接:刷题比赛 题目背景 nodgd是一个喜欢写程序的同学,前不久洛谷OJ横空出世,nodgd同学当然第一时间来到洛谷OJ刷题.于是发生了一系列有趣的事情,他就打算用这些事情来出题恶心大家-- ...