转
https://my.oschina.net/u/1000241/blog/1807703
通过线程池进行任务处理,有时我们需要知道线程池中任务的执行状态。
通过ThreadPoolExecutor的相关API实时获取线程数量,排队任务数量,执行完成线程数量等信息。
private static ExecutorService es = new ThreadPoolExecutor(50, 100, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(
100000
public static void main(String[] args) throws Exception
for (int i = 0; i < 100000; i++) {
es.execute(() -> {
System.out.print(1);
Thread.sleep(1000);
catch(InterruptedException e) {
e.printStackTrace();
ThreadPoolExecutor tpe = ((ThreadPoolExecutor) es);
while (true) {
System.out.println();
int queueSize = tpe.getQueue().size();
System.out.println("当前排队线程数:"+ queueSize);
int activeCount = tpe.getActiveCount();
System.out.println("当前活动线程数:"+ activeCount);
long completedTaskCount = tpe.getCompletedTaskCount();
System.out.println("执行完成线程数:"+ completedTaskCount);
long taskCount = tpe.getTaskCount();
System.out.println("总线程数:"+ taskCount);
Thread.sleep(3000);
转https://my.oschina.net/u/1000241/blog/1807703通过线程池进行任务处理,有时我们需要知道线程池中任务的执行状态。通过ThreadPoolExecutor的相关API实时获取线程数量,排队任务数量,执行完成线程数量等信息。private static ExecutorService es = new ThreadPoolExecutor(5...
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
* @Descri...
线程池好处:
降低资源的消耗, 线程本身是一种资源, 创建和销毁都会消耗CPU内存, 频繁的创建和销毁会浪内存.
提高任务执行的响应速度, 任务执行时, 可以不必等线程创建完成之后在执行,可以直接获取线程执行任务.
提高线程的可管理性, 线程不能无限制的创建, 需要进行统一的分配, 调优和监控.
不适用线程池坏处:
频繁的线程创建和销毁会占用更多的CPU和内积.
频繁的创建和销毁会对gc产生比较大的压力.
线程太多,线程切换带来的开销将不可忽视.
线程太少, 多核CPU得不到充分利用, 浪费资源.
线程池实现原理:
分为三部分: 核心线程池 线程池 队列 拒绝策略
主要流程:
1.判断核心线程池是否已满, 如果不是, 则创建线程执行任务
2.如果核心线程池满了, 判断队列是否满了, 如果队列没满, 将任务放在队列中
3.如果队列满了, 则判断线程池是否已满, 如果没满, 创建线程执行任务
4.如果线程池满了, 按照拒绝策略对任务进行处理
JDK中提供了一个线程池工厂: Executors ,很多工厂方法, 可以创建多种线程池
1.单一线程池
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
该线程池只有一个线程, 若多个任务被提交到此线程池, 那么会被缓冲到队列,当线程空闲时,按照FIFO的方式进行处理.
2.固定数量线程池
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
和创建单一线程池类似, 不同的是线程池中有多个线程, 可以并行处理任务, 若多个线程任务被提交到此线程池, 会有以下执行过程:
如果线程的数量未达到指定数量, 则创建新线程执行任务.
如果线程池的数量达到了指定数量, 而且此时有线程是空闲的, 则取出空闲线程来执行任务.
如果没有线程是空闲的, 则将任务缓冲到队列, 当线程空闲的时候, 按照FIFO的方式进行处理.
FIFO: 先进先出
3.带缓冲的线程池
该线程池中, 核心线程池长度为0, 线程池最大长度为Integer.MAX_VALUE.以SynchronousQueue作为等待队列, 从而每次往队列中插入一个元素, 必须等待另一个线程从这个队列删除一个元素.
4.定时调度的线程池
ExecutorService newCachedThreadPool = Executors.newScheduledThreadPool();
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
ExecutorService newWorkStealingPool = Executors.newWorkStealingPool()
线程池家族
线程池的顶层接口是Executor, 这个接口定义了一个核心方法executor(Runnable command), 这个方法最后被ThreadPoolExecutor类实现, 这个方法用来传入任务, 并且该类是线程池的核心类, 构造方法如下 :
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueu, ThreadFactory threadFactory,RejectedExecutionHandler handler);
参数意义:
CorePoolSize: 核心线程池大小, 如果核心线程池有空闲的位置, 新的任务就会被核心线程池新建一个线程执行, 执行完毕不会销毁线程, 线程会进入缓冲队列等待再次被运行
MaximunPoolSize: 线程池能创建最大的线程数量, 如果核心线程池和缓冲队列都已经满了, 新的任务就会进来创建新的线程来执行, 但是数量不能超过maximunPoolSize, 否则采取拒绝接受任务策略
KeepAliveTime: 非核心线程能够空闲的最长时间, 超过时间, 线程终止, 这个参数默认只有在线程数量超过核心线程池大小时, 才会起作用.
Unit: 时间单位, 和keepAliveTime配合使用.
WorkQueue: 缓冲队列, 用来存放等待被执行的任务
ThreadFactory: 线程工厂, 用来创建线程, 一般有三个选择
ArrayBlockingQueue LinkedBlockingQueue SynchronousQueue
Handler :拒绝处理策略, 线程数量大于最大线程数量就会拒绝处理策略, 四种策略为
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
Executor接口有一个子接口ExecutorService, ExecutorService的实现类AbstracExecutorService, 而ThreadPoolExecutor正是AbstracExecutorService的子类.
ThreadPoolExecutor还有两个常用的方法shutdown和submit,两者都用来关闭线程池, 但是submit有一个结果返回.
线程池任务执行
当执行executor(Runnable command)方法后, 传入一个任务,
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
else if (!addIfUnderMaximumPoolSize(command))
reject(command);
(传入的对象实现了Runnable接口, 这就解释了: 为什么实现多线程的方式中继承Thread类不能使用线程池了)
执行过程:
首先判断任务是否为空, 为空抛出空指针异常, 否则执行下一个判断: 当前线程数量是否小于核心线程池线程数量, 是,则执行addIfUbderCorePollSize(command)方法, 在核心线程池中创建新的线程, 并且执行这个任务
如果当前线程池中的数目小于corePollSize, 则每来一个任务, 就会创建一个新的线程去执行这个任务
如果当前线程池中的线程数目>=corePollSize, 则每来一个任务, 会尝试将其添加到缓冲队列中, 如果添加成功, 则该任务会等待空闲线程将其取出去执行, 如果添加失败(一般是以为任务队列已经满了), 则会尝试创建新的线程去执行这个任务.
如果当前线程池中的线程数目达到maximunPoolSize, 则会采取任务拒绝策略进行处理.
如果线程池中的线程数量大于corePoolSize时, 如果某线程空闲时间超过keepAliveTime, 线程将被终止, 直至线程池中的线程数目小于等于corePoolSize; 如果允许核心池中的线程设置存活时间, 那么核心池中的线程空闲时间超过keepAliveTime, 线程也会被终止.
简单的异步本地执行器Enlightware:registered: 软件。概述一个基于单线程轮询的执行器,适用于游戏、嵌入式系统或 WASM。当任务数量很少或有一小部分被阻塞时,这个执行器会很有用。基于轮询,在一般情况下,它会为了简单而牺牲效率,并且不需要任何并发原语,例如Arc等。用法要使用这个 crate,首先将它添加到你的Cargo.toml :[dependencies ]simple-async-local-executor =" 0.1.0"然后,请参阅文档以获取更多详细信息。默认情况下,此 crate 依赖于futures-0.3以提供FusedFuture特性。如果您不需要它,您可以禁用futures功能并避免这种依赖性。例子除了文档和单元测试之外,还提供了以下示例:examples/game-units.rs :使用异步以友好的方式实现单元行为执照获得许可Apache 许可证,版本 2.0(LICENSE-APACHE或http://www.apache.org/licenses/LICENSE-2.0 )MIT 许可证(LICENSE-MIT或http://opensource.org/
享有全球盛誉的编程专家Jeffrey Richter,这位与Microsoft .NET开发团队合作长达8年时间的资深顾问,在本书中和读者分享他编程生涯中积累的所有丰富经验和心得,他的独到、睿智的见解,他的远见卓识,为开发人员构建健壮、可靠和具有良好响应能力的应用程序与组件奠定了良好的基础。
《CLR via C#(第3版) 》针对.NET Framework 4.0和多核编程进行了全面更新和修订,是帮助读者深入探索和掌握公共语言运行时、C#和.NET开发的重要参考,同时也是帮助开发人员构建任何一种应用程序(如Microsoft Silverlight、ASP.NET、Windows Prensentation Foundation、Web服务和控制台应用程序)的良师益友。
本书涵括以下主题:
· 构建、部署应用程序、组件和共享程序集,并对它们进行版本管理
· 理解基元类型、值类型和引用类型的行为,从而最高效地定义和使用它们
· 使用泛型和接口来定义可重用的算法
· 高效使用特定的CLR类型——委托、枚举、定制attribute、数组和字符串
· 理解垃圾回收器是如何管理内存资源的
· 使用线程池、任务、取消、计时器和异步I/O操作来设计响应性强、稳定性高和伸缩性大的解决方案
· 借助于异常处理来进行状态管理
· 使用CLR寄宿、AppDomain、程序集加载、反射和C#的dynamic类型来构造具有动态扩展能力的应用程序
本书作者作者Jeffrey Richter,.NET和Windows编程领域当之无愧的大师和权威,以著述清楚明了,行文流水,言简意赅著称,在国内具有相当高的知名度,他的著作之一《Windows核心编程(第5版)》屡获殊荣,在国内外都享有盛誉,在国内因年销量过万而获得中国书刊业发行协会“2009年度全行业畅销书品种”称号。
第1章 CLR的执行模型
1.1 将源代码编译成托管模块
1.2 将托管模块合并成程序集
1.3 加载公共语言运行时
1.4 执行程序集的代码
1.4.1 IL和验证
1.4.2 不安全的代码
1.5 本地代码生成器:NGen.exe
1.6 Framework类库
1.7 通用类型系统
1.8 公共语言规范
1.9 与非托管代码的互操作性
第2章 生成、打包、部署和管理应用程序及类型
2.1 .NET Framework部署目标
2.2 将类型生成到模块中
2.2.1 响应文件
2.3 元数据概述
2.4 将模块合并成程序集
2.4.1 使用Visual Studio IDE将程序集添加到项目中
2.4.2 使用程序集链接器
2.4.3 为程序集添加资源文件
2.5 程序集版本资源信息
2.5.1 版本号
2.6 语言文化
2.7 简单应用程序部署(私有部署的程序集)
2.8 简单管理控制(配置)
第3章 共享程序集和强命名程序集
3.1 两种程序集,两种部署
3.2 为程序集分配强名称
3.3 全局程序集缓存
3.4 在生成的程序集中引用一个强命名程序集
3.5 强命名程序集能防范篡改
3.6 延迟签名
3.7 私有部署强命名程序集
3.8 “运行时”如何解析类型引用
3.9 高级管理控制(配置)
3.9.1 发布者策略控制
第4章 类 型 基 础
4.1 所有类型都从System.Object派生
4.2 类型转换
4.2.1 使用C#的is和as操作符来转型
4.3 命名空间和程序集
4.4 运行时的相互联系
第5章 基元类型、引用类型和值类型
5.1 编程语言的基元类型
5.1.1 checked和unchecked基元类型操作
5.2 引用类型和值类型
5.3 值类型的装箱和拆箱
5.3.1 使用接口更改已装箱值类型中的字段(以及为什么不应该这样做)
5.3.2 对象相等性和同一性
5.4 对象哈希码
5.5 dynamic基元类型
第6章 类型和成员基础
6.1 类型的各种成员
6.2 类型的可见性
6.2.1 友元程序集
6.3 成员的可访问性
6.4 静态类
6.5 分部类、结构和接口
6.6 组件、多态和版本控制
6.6.1 CLR如何调用虚方法、属性和事件
6.6.2 合理使用类型的可见性和成员的可访问性
6.6.3 对类型进行版本控制时的虚方法的处理
第7章 常量和字段
7.1 常量
7.2 字段
第8章 方法
8.1 实例构造器和类(引用类型)
8.2 实例构造器和结构(值类型)
8.3 类型构造器
8.3.1 类型构造器的性能
8.4 操作符重载方法
8.4.1 操作符和编程语言互操作性
8.5 转换操作符方法
8.6 扩展方法
8.6.1 规则和原则
8.6.2 用扩展方法扩展各种类型
8.6.3 ExtensionAttribute
8.7 分部方法
8.7.1 规则和原则
第9章 参 数
9.1 可选参数和命名参数
9.1.1 规则和原则
9.1.2 DefaultParameterValueAttribute和OptionalAttribute
9.2 隐式类型的局部变量
9.3 以传引用的方式向方法传递参数
9.4 向方法传递可变数量的参数
9.5 参数和返回类型的指导原则
9.6 常量性
第10章 属性
10.1 无参属性
10.1.1 自动实现的属性
10.1.2 合理定义属性
10.1.3 对象和集合初始化器
10.1.4 匿名类型
10.1.5 System.Tuple类型
10.2 有参属性
10.3 调用属性访问器方法时的性能
10.4 属性访问器的可访问性
10.5 泛型属性访问器方法
第11章 事件
11.1 设计要公开事件的类型
11.1.1 第一步:定义类型来容纳所有需要发送给事件通知接收者的附加信息
11.1.2 第二步:定义事件成员
11.1.3 第三步:定义负责引发事件的方法来通知事件的登记对象
11.1.4 第四步:定义方法将输入转化为期望事件
11.2 编译器如何实现事件
11.3 设计侦听事件的类型
11.4 显式实现事件
第12章 泛型
12.1 Framework类库中的泛型
12.2 Wintellect的Power Collections库
12.3 泛型基础结构
12.3.1 开放和封闭类型
12.3.2 泛型类型和继承
12.3.3 泛型类型同一性
12.3.4 代码爆炸
12.4 泛型接口
12.5 泛型委托
12.6 委托和接口的逆变和协变泛型类型实参
12.7 泛型方法
12.7.1 泛型方法和类型推断
12.8 泛型和其他成员
12.9 可验证性和约束
12.9.1 主要约束
12.9.2 次要约束
12.9.3 构造器约束
12.9.4 其他可验证性问题
第13章 接口
13.1 类和接口继承
13.2 定义接口
13.3 继承接口
13.4 关于调用接口方法的更多探讨
13.5 隐式和显式接口方法实现(幕后发生的事情)
13.6 泛型接口
13.7 泛型和接口约束
13.8 实现多个具有相同方法名和签名的接口
13.9 用显式接口方法实现来增强编译时类型安全性
13.10 谨慎使用显式接口方法实现
13.11 设计:基类还是接口?
第14章 字符、字符串和文本处理
14.1 字符
14.2 System.String类型
14.2.1 构造字符串
14.2.2 字符串是不可变的
14.2.3 比较字符串
14.2.4 字符串留用
14.2.5 字符串池
14.2.6 检查字符串中的字符和文本元素
14.2.7 其他字符串操作
14.3 高效率构造字符串
14.3.1 构造StringBuilder对象
14.3.2 StringBuilder的成员
14.4 获取对象的字符串表示:ToString
14.4.1 指定具体的格式和语言文化
14.4.2 将多个对象格式成一个字符串
14.4.3 提供定制格式化器
14.5 解析字符串来获取对象:Parse
14.6 编码:字符和字节的相互转换
14.6.1 字符和字节流的编码和解码
14.6.2 Base-64字符串编码和解码
14.7 安全字符串
第15章 枚举类型和位标志
15.1 枚举类型
15.2 位标志
15.3 向枚举类型添加方法
第16章 数组
16.1 初始化数组元素
16.2 数组转型
16.3 所有数组都隐式派生自System.Array
16.4 所有数组都隐式实现IEnumerable,Icollection和IList
16.5 数组的传递和返回
16.6 创建下限非0的数组
16.7 数组的访问性能
16.8 不安全的数组访问和固定大小的数组
第17章 委托
17.1 初识委托
17.2 用委托回调静态方法
17.3 用委托回调实例方法
17.4 委托揭秘
17.5 用委托回调许多方法(委托链)
17.5.1 C#对委托链的支持
17.5.2 取得对委托链调用的更多控制
17.6 委托定义太多啦(泛型委托)
17.7 C#为委托提供的简化语法
17.7.1 简化语法1:不需要构造委托对象
17.7.2 简化语法2:不需要定义回调方法
17.7.3 简化语法3:局部变量不需要手动包装到类中即可传给回调方法
17.8 委托和反射
第18章 定制attribute
18.1 使用定制attribute
18.2 定义自己的attribute类
18.3 attribute的构造器和字段/属性的数据类型
18.4 检测定制attribute
18.5 两个attribute实例的相互匹配
18.6 检测定制attribute时不创建从Attribute派生的对象
18.7 条件attribute类
第19章 可空值类型
19.1 C#对可空值类型的支持
19.2 C#的空接合操作符
19.3 CLR对可空值类型的特殊支持
19.3.1 可空值类型的装箱
19.3.2 可空值类型的拆箱
19.3.3 通过可空值类型调用GetType
19.3.4 通过可空值类型调用接口方法
第20章 异常和状态管理
20.1 定义“异常”
20.2 异常处理机制
20.2.1 try块
20.2.2 catch块
20.2.3 finally块
20.3 System.Exception类
20.4 FCL定义的异常类
20.5 抛出异常
20.6 定义自己的异常类
20.7 用可靠性换取开发效率
20.8 指导原则和最佳实践
20.8.1 善用finally块
20.8.2 不要什么都捕捉
20.8.3 得体地从异常中恢复
20.8.4 发生不可恢复的异常时回滚部分完成的操作——维持状态
20.8.5 隐藏实现细节来维系契约
20.9 未处理的异常
20.10 对异常进行调试
20.11 异常处理的性能问题
20.12 约束执行区域(CER)
20.13 代码契约
第21章 自动内存管理(垃圾回收)
21.1 理解垃圾回收平台的基本工作原理
21.1.1 从托管堆分配资源
21.2 垃圾回收算法
21.3 垃圾回收与调试
21.4 使用终结操作来释放本地资源
21.4.1 使用CriticalFinalizerObject类型确保终结
21.4.2 SafeHandle类型及其派生类型
21.4.3 使用SafeHandle类型与非托管代码进行互操作
21.5 对托管资源使用终结操作
21.6 什么会导致调用Finalize方法
21.7 终结揭秘
21.8 Dispose模式:强制对象清理资源
21.9 使用实现了Dispose模式的类型
21.10 C#的using语句
21.11 一个有趣的依赖性问题
21.12 手动监视和控制对象的生存期
21.13 对象复活
21.14 代
21.15 用于本地资源的其他垃圾回收功能
21.16 预测需求大量内存的操作能否成功
21.17 编程控制垃圾回收器
21.18 线程劫持
21.19 垃圾回收模式
21.20 大对象
21.21 监视垃圾回收
第22章 CLR寄宿和AppDomain
22.1 CLR寄宿
22.2 AppDomain
22.2.1 跨越AppDomain边界访问对象
22.3 卸载AppDomain
22.4 监视AppDomain
22.5 AppDomain FirstChance异常通知
22.6 宿主如何使用AppDomain
22.6.1 可执行应用程序
22.6.2 Microsoft Silverlight富Internet应用程序
22.6.3 Microsoft ASP.NET Web窗体和XML Web服务应用程序
22.6.4 Microsoft SQL Server
22.6.5 更多的用法只局限于你自己的想象力
22.7 高级宿主控制
22.7.1 使用托管代码管理CLR
22.7.2 编写健壮的宿主应用程序
22.7.3 宿主如何拿回它的线程
第23章 程序集加载和反射
23.1 程序集加载
23.2 使用反射构建动态可扩展应用程序
23.3 反射的性能
23.3.1 发现程序集中定义的类型
23.3.2 类型对象的准确含义
23.3.3 构建Exception派生类型的一个层次结构
23.3.4 构造类型的实例
23.4 设计支持加载项的应用程序
23.5 使用反射发现类型的成员
23.5.1 发现类型成员
23.5.2 BindingFlags:筛选返回的成员种类
23.5.3 发现类型的接口
23.5.4 调用类型的成员
23.5.5 一次绑定、多次调用
23.5.6 使用绑定句柄来减少进程的内存耗用
第24章 运行时序列化
24.1 序列化/反序列化快速入门
24.2 使类型可序列化
24.3 控制序列化和反序列化
24.4 格式化器如何序列化类型实例
24.5 控制序列化/反序列化的数据
24.5.1 如何在基类没有实现ISerializable的前提下定义一个实现它的类型
24.6 流上下文
24.7 将类型序列化为不同的类型以及将对象反序列化为不同的对象
24.8 序列化代理
24.8.1 代理选择器链
24.9 反序列化对象时重写程序集和/或类型
第25章 线程基础
25.1 Windows为什么要支持线程
25.2 线程开销
25.3 停止疯狂
25.4 CPU发展趋势
25.5 NUMA架构的机器
25.6 CLR线程和Windows线程
25.7 使用专用线程执行异步的计算限制操作
25.8 使用线程的理由
25.9 线程调度和优先级
25.10 前台线程和后台线程
25.11 继续学习
第26章 计算限制的异步操作
26.1 CLR线程池基础
26.2 执行简单的计算限制操作
26.3 执行上下文
26.4 协作式取消
26.5 任务
26.5.1 等待任务完成并获取它的结果
26.5.2 取消任务
26.5.3 一个任务完成时自动启动一个新任务
26.5.4 任务可以启动子任务
26.5.5 任务内部揭秘
26.5.6 任务工厂
26.5.7 任务调度器
26.6 Parallel的静态For,ForEach和Invoke方法
26.7 并行语言集成查询(PLINQ)
26.8 执行定时计算限制操作
26.8.1 太多的计时器,太少的时间
26.9 线程池如何管理线程
26.9.1 设置线程池限制
26.9.2 如何管理工作者线程
26.10 缓存线和伪共享
第27章 I/O限制的异步操作
27.1 Windows如何执行I/O操作
27.2 CLR的异步编程模型(APM)
27.3 AsyncEnumerator类
27.4 APM和异常
27.5 应用程序及其线程处理模型
27.6 异步实现服务器
27.7 APM和计算限制操作
27.8 APM的注意事项
27.8.1 在没有线程池的前提下使用APM
27.8.2 总是调用EndXxx方法,而且只调用一次
27.8.3 调用EndXxx方法时总是使用相同的对象
27.8.4 为BeginXxx和EndXxx方法使用ref,out和params实参
27.8.5 不能取消异步I/O限制操作
27.8.6 内存消耗
27.8.7 有的I/O操作必须同步完成
27.8.8 FileStream特有的问题
27.9 I/O请求优先级
27.10 将IAsyncResult APM转换为Task
27.11 基于事件的异步模式
27.11.1 将EAP转换为Task
27.11.2 APM和EAP的对比
27.12 编程模型的泥沼
第28章 基元线程同步构造
28.1 类库和线程安全
28.2 基元用户模式和内核模式构造
28.3 用户模式构造
28.3.1 易失构造
28.3.2 互锁构造
28.3.3 实现简单的Spin Lock
28.3.4 Interlocked Anything模式
28.4 内核模式构造
28.4.1 Event构造
28.4.2 Semaphore构造
28.4.3 Mutex构造
28.4.4 在一个内核构造可用时调用一个方法
第29章 混合线程同步构造
29.1 一个简单的混合锁
29.2 自旋、线程所有权和递归
29.3 混合构造的大杂烩
29.3.1 ManualResetEventSlim和SemaphoreSlim类
29.3.2 Monitor类和同步块
29.3.3 ReaderWriterLockSlim类
29.3.4 OneManyLock类
29.3.5 CountdownEvent类
29.3.6 Barrier类
29.3.7 线程同步构造小结
29.4 著名的双检锁技术
29.5 条件变量模式
29.6 用集合防止占有锁太长的时间
29.7 并发集合类
享有全球盛誉的编程专家Jeffrey Richter,这位与Microsoft .NET开发团队合作长达8年时间的资深顾问,在本书中和读者分享他编程生涯中积累的所有丰富经验和心得,他的独到、睿智的见解,他的远见卓识,为开发人员构建健壮、可靠和具有良好响应能力的应用程序与组件奠定了良好的基础。
《CLR via C#(第3版) 》针对.NET Framework 4.0和多核编程进行了全面更新和修订,是帮助读者深入探索和掌握公共语言运行时、C#和.NET开发的重要参考,同时也是帮助开发人员构建任何一种应用程序(如Microsoft Silverlight、ASP.NET、Windows Prensentation Foundation、Web服务和控制台应用程序)的良师益友。
本书涵括以下主题:
· 构建、部署应用程序、组件和共享程序集,并对它们进行版本管理
· 理解基元类型、值类型和引用类型的行为,从而最高效地定义和使用它们
· 使用泛型和接口来定义可重用的算法
· 高效使用特定的CLR类型——委托、枚举、定制attribute、数组和字符串
· 理解垃圾回收器是如何管理内存资源的
· 使用线程池、任务、取消、计时器和异步I/O操作来设计响应性强、稳定性高和伸缩性大的解决方案
· 借助于异常处理来进行状态管理
· 使用CLR寄宿、AppDomain、程序集加载、反射和C#的dynamic类型来构造具有动态扩展能力的应用程序
本书作者作者Jeffrey Richter,.NET和Windows编程领域当之无愧的大师和权威,以著述清楚明了,行文流水,言简意赅著称,在国内具有相当高的知名度,他的著作之一《Windows核心编程(第5版)》屡获殊荣,在国内外都享有盛誉,在国内因年销量过万而获得中国书刊业发行协会“2009年度全行业畅销书品种”称号。
第1章 CLR的执行模型
1.1 将源代码编译成托管模块
1.2 将托管模块合并成程序集
1.3 加载公共语言运行时
1.4 执行程序集的代码
1.4.1 IL和验证
1.4.2 不安全的代码
1.5 本地代码生成器:NGen.exe
1.6 Framework类库
1.7 通用类型系统
1.8 公共语言规范
1.9 与非托管代码的互操作性
第2章 生成、打包、部署和管理应用程序及类型
2.1 .NET Framework部署目标
2.2 将类型生成到模块中
2.2.1 响应文件
2.3 元数据概述
2.4 将模块合并成程序集
2.4.1 使用Visual Studio IDE将程序集添加到项目中
2.4.2 使用程序集链接器
2.4.3 为程序集添加资源文件
2.5 程序集版本资源信息
2.5.1 版本号
2.6 语言文化
2.7 简单应用程序部署(私有部署的程序集)
2.8 简单管理控制(配置)
第3章 共享程序集和强命名程序集
3.1 两种程序集,两种部署
3.2 为程序集分配强名称
3.3 全局程序集缓存
3.4 在生成的程序集中引用一个强命名程序集
3.5 强命名程序集能防范篡改
3.6 延迟签名
3.7 私有部署强命名程序集
3.8 “运行时”如何解析类型引用
3.9 高级管理控制(配置)
3.9.1 发布者策略控制
第4章 类 型 基 础
4.1 所有类型都从System.Object派生
4.2 类型转换
4.2.1 使用C#的is和as操作符来转型
4.3 命名空间和程序集
4.4 运行时的相互联系
第5章 基元类型、引用类型和值类型
5.1 编程语言的基元类型
5.1.1 checked和unchecked基元类型操作
5.2 引用类型和值类型
5.3 值类型的装箱和拆箱
5.3.1 使用接口更改已装箱值类型中的字段(以及为什么不应该这样做)
5.3.2 对象相等性和同一性
5.4 对象哈希码
5.5 dynamic基元类型
第6章 类型和成员基础
6.1 类型的各种成员
6.2 类型的可见性
6.2.1 友元程序集
6.3 成员的可访问性
6.4 静态类
6.5 分部类、结构和接口
6.6 组件、多态和版本控制
6.6.1 CLR如何调用虚方法、属性和事件
6.6.2 合理使用类型的可见性和成员的可访问性
6.6.3 对类型进行版本控制时的虚方法的处理
第7章 常量和字段
7.1 常量
7.2 字段
第8章 方法
8.1 实例构造器和类(引用类型)
8.2 实例构造器和结构(值类型)
8.3 类型构造器
8.3.1 类型构造器的性能
8.4 操作符重载方法
8.4.1 操作符和编程语言互操作性
8.5 转换操作符方法
8.6 扩展方法
8.6.1 规则和原则
8.6.2 用扩展方法扩展各种类型
8.6.3 ExtensionAttribute
8.7 分部方法
8.7.1 规则和原则
第9章 参 数
9.1 可选参数和命名参数
9.1.1 规则和原则
9.1.2 DefaultParameterValueAttribute和OptionalAttribute
9.2 隐式类型的局部变量
9.3 以传引用的方式向方法传递参数
9.4 向方法传递可变数量的参数
9.5 参数和返回类型的指导原则
9.6 常量性
第10章 属性
10.1 无参属性
10.1.1 自动实现的属性
10.1.2 合理定义属性
10.1.3 对象和集合初始化器
10.1.4 匿名类型
10.1.5 System.Tuple类型
10.2 有参属性
10.3 调用属性访问器方法时的性能
10.4 属性访问器的可访问性
10.5 泛型属性访问器方法
第11章 事件
11.1 设计要公开事件的类型
11.1.1 第一步:定义类型来容纳所有需要发送给事件通知接收者的附加信息
11.1.2 第二步:定义事件成员
11.1.3 第三步:定义负责引发事件的方法来通知事件的登记对象
11.1.4 第四步:定义方法将输入转化为期望事件
11.2 编译器如何实现事件
11.3 设计侦听事件的类型
11.4 显式实现事件
第12章 泛型
12.1 Framework类库中的泛型
12.2 Wintellect的Power Collections库
12.3 泛型基础结构
12.3.1 开放和封闭类型
12.3.2 泛型类型和继承
12.3.3 泛型类型同一性
12.3.4 代码爆炸
12.4 泛型接口
12.5 泛型委托
12.6 委托和接口的逆变和协变泛型类型实参
12.7 泛型方法
12.7.1 泛型方法和类型推断
12.8 泛型和其他成员
12.9 可验证性和约束
12.9.1 主要约束
12.9.2 次要约束
12.9.3 构造器约束
12.9.4 其他可验证性问题
第13章 接口
13.1 类和接口继承
13.2 定义接口
13.3 继承接口
13.4 关于调用接口方法的更多探讨
13.5 隐式和显式接口方法实现(幕后发生的事情)
13.6 泛型接口
13.7 泛型和接口约束
13.8 实现多个具有相同方法名和签名的接口
13.9 用显式接口方法实现来增强编译时类型安全性
13.10 谨慎使用显式接口方法实现
13.11 设计:基类还是接口?
第14章 字符、字符串和文本处理
14.1 字符
14.2 System.String类型
14.2.1 构造字符串
14.2.2 字符串是不可变的
14.2.3 比较字符串
14.2.4 字符串留用
14.2.5 字符串池
14.2.6 检查字符串中的字符和文本元素
14.2.7 其他字符串操作
14.3 高效率构造字符串
14.3.1 构造StringBuilder对象
14.3.2 StringBuilder的成员
14.4 获取对象的字符串表示:ToString
14.4.1 指定具体的格式和语言文化
14.4.2 将多个对象格式成一个字符串
14.4.3 提供定制格式化器
14.5 解析字符串来获取对象:Parse
14.6 编码:字符和字节的相互转换
14.6.1 字符和字节流的编码和解码
14.6.2 Base-64字符串编码和解码
14.7 安全字符串
第15章 枚举类型和位标志
15.1 枚举类型
15.2 位标志
15.3 向枚举类型添加方法
第16章 数组
16.1 初始化数组元素
16.2 数组转型
16.3 所有数组都隐式派生自System.Array
16.4 所有数组都隐式实现IEnumerable,Icollection和IList
16.5 数组的传递和返回
16.6 创建下限非0的数组
16.7 数组的访问性能
16.8 不安全的数组访问和固定大小的数组
第17章 委托
17.1 初识委托
17.2 用委托回调静态方法
17.3 用委托回调实例方法
17.4 委托揭秘
17.5 用委托回调许多方法(委托链)
17.5.1 C#对委托链的支持
17.5.2 取得对委托链调用的更多控制
17.6 委托定义太多啦(泛型委托)
17.7 C#为委托提供的简化语法
17.7.1 简化语法1:不需要构造委托对象
17.7.2 简化语法2:不需要定义回调方法
17.7.3 简化语法3:局部变量不需要手动包装到类中即可传给回调方法
17.8 委托和反射
第18章 定制attribute
18.1 使用定制attribute
18.2 定义自己的attribute类
18.3 attribute的构造器和字段/属性的数据类型
18.4 检测定制attribute
18.5 两个attribute实例的相互匹配
18.6 检测定制attribute时不创建从Attribute派生的对象
18.7 条件attribute类
第19章 可空值类型
19.1 C#对可空值类型的支持
19.2 C#的空接合操作符
19.3 CLR对可空值类型的特殊支持
19.3.1 可空值类型的装箱
19.3.2 可空值类型的拆箱
19.3.3 通过可空值类型调用GetType
19.3.4 通过可空值类型调用接口方法
第20章 异常和状态管理
20.1 定义“异常”
20.2 异常处理机制
20.2.1 try块
20.2.2 catch块
20.2.3 finally块
20.3 System.Exception类
20.4 FCL定义的异常类
20.5 抛出异常
20.6 定义自己的异常类
20.7 用可靠性换取开发效率
20.8 指导原则和最佳实践
20.8.1 善用finally块
20.8.2 不要什么都捕捉
20.8.3 得体地从异常中恢复
20.8.4 发生不可恢复的异常时回滚部分完成的操作——维持状态
20.8.5 隐藏实现细节来维系契约
20.9 未处理的异常
20.10 对异常进行调试
20.11 异常处理的性能问题
20.12 约束执行区域(CER)
20.13 代码契约
第21章 自动内存管理(垃圾回收)
21.1 理解垃圾回收平台的基本工作原理
21.1.1 从托管堆分配资源
21.2 垃圾回收算法
21.3 垃圾回收与调试
21.4 使用终结操作来释放本地资源
21.4.1 使用CriticalFinalizerObject类型确保终结
21.4.2 SafeHandle类型及其派生类型
21.4.3 使用SafeHandle类型与非托管代码进行互操作
21.5 对托管资源使用终结操作
21.6 什么会导致调用Finalize方法
21.7 终结揭秘
21.8 Dispose模式:强制对象清理资源
21.9 使用实现了Dispose模式的类型
21.10 C#的using语句
21.11 一个有趣的依赖性问题
21.12 手动监视和控制对象的生存期
21.13 对象复活
21.14 代
21.15 用于本地资源的其他垃圾回收功能
21.16 预测需求大量内存的操作能否成功
21.17 编程控制垃圾回收器
21.18 线程劫持
21.19 垃圾回收模式
21.20 大对象
21.21 监视垃圾回收
第22章 CLR寄宿和AppDomain
22.1 CLR寄宿
22.2 AppDomain
22.2.1 跨越AppDomain边界访问对象
22.3 卸载AppDomain
22.4 监视AppDomain
22.5 AppDomain FirstChance异常通知
22.6 宿主如何使用AppDomain
22.6.1 可执行应用程序
22.6.2 Microsoft Silverlight富Internet应用程序
22.6.3 Microsoft ASP.NET Web窗体和XML Web服务应用程序
22.6.4 Microsoft SQL Server
22.6.5 更多的用法只局限于你自己的想象力
22.7 高级宿主控制
22.7.1 使用托管代码管理CLR
22.7.2 编写健壮的宿主应用程序
22.7.3 宿主如何拿回它的线程
第23章 程序集加载和反射
23.1 程序集加载
23.2 使用反射构建动态可扩展应用程序
23.3 反射的性能
23.3.1 发现程序集中定义的类型
23.3.2 类型对象的准确含义
23.3.3 构建Exception派生类型的一个层次结构
23.3.4 构造类型的实例
23.4 设计支持加载项的应用程序
23.5 使用反射发现类型的成员
23.5.1 发现类型成员
23.5.2 BindingFlags:筛选返回的成员种类
23.5.3 发现类型的接口
23.5.4 调用类型的成员
23.5.5 一次绑定、多次调用
23.5.6 使用绑定句柄来减少进程的内存耗用
第24章 运行时序列化
24.1 序列化/反序列化快速入门
24.2 使类型可序列化
24.3 控制序列化和反序列化
24.4 格式化器如何序列化类型实例
24.5 控制序列化/反序列化的数据
24.5.1 如何在基类没有实现ISerializable的前提下定义一个实现它的类型
24.6 流上下文
24.7 将类型序列化为不同的类型以及将对象反序列化为不同的对象
24.8 序列化代理
24.8.1 代理选择器链
24.9 反序列化对象时重写程序集和/或类型
第25章 线程基础
25.1 Windows为什么要支持线程
25.2 线程开销
25.3 停止疯狂
25.4 CPU发展趋势
25.5 NUMA架构的机器
25.6 CLR线程和Windows线程
25.7 使用专用线程执行异步的计算限制操作
25.8 使用线程的理由
25.9 线程调度和优先级
25.10 前台线程和后台线程
25.11 继续学习
第26章 计算限制的异步操作
26.1 CLR线程池基础
26.2 执行简单的计算限制操作
26.3 执行上下文
26.4 协作式取消
26.5 任务
26.5.1 等待任务完成并获取它的结果
26.5.2 取消任务
26.5.3 一个任务完成时自动启动一个新任务
26.5.4 任务可以启动子任务
26.5.5 任务内部揭秘
26.5.6 任务工厂
26.5.7 任务调度器
26.6 Parallel的静态For,ForEach和Invoke方法
26.7 并行语言集成查询(PLINQ)
26.8 执行定时计算限制操作
26.8.1 太多的计时器,太少的时间
26.9 线程池如何管理线程
26.9.1 设置线程池限制
26.9.2 如何管理工作者线程
26.10 缓存线和伪共享
第27章 I/O限制的异步操作
27.1 Windows如何执行I/O操作
27.2 CLR的异步编程模型(APM)
27.3 AsyncEnumerator类
27.4 APM和异常
27.5 应用程序及其线程处理模型
27.6 异步实现服务器
27.7 APM和计算限制操作
27.8 APM的注意事项
27.8.1 在没有线程池的前提下使用APM
27.8.2 总是调用EndXxx方法,而且只调用一次
27.8.3 调用EndXxx方法时总是使用相同的对象
27.8.4 为BeginXxx和EndXxx方法使用ref,out和params实参
27.8.5 不能取消异步I/O限制操作
27.8.6 内存消耗
27.8.7 有的I/O操作必须同步完成
27.8.8 FileStream特有的问题
27.9 I/O请求优先级
27.10 将IAsyncResult APM转换为Task
27.11 基于事件的异步模式
27.11.1 将EAP转换为Task
27.11.2 APM和EAP的对比
27.12 编程模型的泥沼
第28章 基元线程同步构造
28.1 类库和线程安全
28.2 基元用户模式和内核模式构造
28.3 用户模式构造
28.3.1 易失构造
28.3.2 互锁构造
28.3.3 实现简单的Spin Lock
28.3.4 Interlocked Anything模式
28.4 内核模式构造
28.4.1 Event构造
28.4.2 Semaphore构造
28.4.3 Mutex构造
28.4.4 在一个内核构造可用时调用一个方法
第29章 混合线程同步构造
29.1 一个简单的混合锁
29.2 自旋、线程所有权和递归
29.3 混合构造的大杂烩
29.3.1 ManualResetEventSlim和SemaphoreSlim类
29.3.2 Monitor类和同步块
29.3.3 ReaderWriterLockSlim类
29.3.4 OneManyLock类
29.3.5 CountdownEvent类
29.3.6 Barrier类
29.3.7 线程同步构造小结
29.4 著名的双检锁技术
29.5 条件变量模式
29.6 用集合防止占有锁太长的时间
29.7 并发集合类
//创建线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
//声明执行结果
线程的优势
(1)如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统吞吐率
(2)建模简单:通过使用线程可以讲复杂并且异步的工作流进一步分解成一组简单并且同步的工作流,每个工作流在一个单独的线程中运行,并在特定的同步位置交互
(3)简化异步事件的处理:服务器应用程序在接受来自多个远程客户端的请求时,如果为每个连接都分配一个线程并且使用同步IO,就会降低开发难度
(4)用户界面具备更短的响应时间:现代GUI框架中大都使用一个事件分发线程(类似于中断响应函数)来替代主事件循环,当用户界面用有事件发生时,在事件线程中将调用对应的事件处理函数(类似于中断处理函数)
线程的风险
线程安全性:永远不发生糟糕的事情
活跃性问题:某件正确的事情迟早会发生
问题:希望正确的事情尽快发生
服务时间过长
响应不灵敏
吞吐率过低
资源消耗过高
可伸缩性较低
线程的应用场景
Timer
确保TimerTask访问的对象本身是线程安全的
Servlet和JSP
Servlet本身要是线程安全的
正确协同一个Servlet访问多个Servlet共享的信息
远程方法调用(RMI)
正确协同多个对象中的共享状态
正确协同远程对象本身状态的访问
Swing和AWT
事件处理器与访问共享状态的其他代码都要采取线程安全的方式实现
框架通过在框架线程中调用应用程序代码将并发性引入应用程序,因此对线程安全的需求在整个应用程序中都需要考虑
线程安全性
当多个线程访问某个类时,这个类始终能表现出正确的行为,那么就称这个类是线程安全的
无状态对象一定是线程安全的,大多数Servlet都是无状态的
一组不可分割的操作
基于一种可能失效的观察结果来做出判断或执行某个计算
复合操作:执行复合操作期间,要持有锁
加锁机制、用锁保护状态、实现共享访问
锁的不恰当使用可能会引起程序性能下降
对象的共享使用策略
线程封闭:线程封闭的对象只能由一个线程拥有并修改
Ad-hoc线程封闭
ThreadLocal类
只读共享:不变对象一定是线程安全的
尽量将域声明为final类型,除非它们必须是可变的
不可变对象
事实不可变对象
线程安全共享
封装有助于管理复杂度
线程安全的对象在其内部实现同步,因此多个接口可以通过公有接口来进行访问
保护对象:被保护的对象只能通过特定的锁来访问
将对象封装到线程安全对象中
由特定锁保护
保护对象的方法
对象的组合
设计线程安全的类
线程安全的委托
委托是创建线程安全类的最有效策略,只需要让现有的线程安全类管理所有的状态
在现有线程安全类中添加功能
将同步策略文档化
基础构建模块
同步容器类
Vector
Hashtable
实现线程安全的方式
将状态封装起来,对每个公有方法都进行同步
存在的问题
客户端加锁
ConcurrentHashMap
用于替代同步且基于散列的Map
CopyOnWriteArrayList
用于在遍历操作为主要操作的情况下替代同步的List
Queue
ConcurrentLinkedQueue
*BlockingQueue
提供了可阻塞的put和take方法
生产者-消费者模式
中断的处理策略
传递InterruptedException
恢复中断,让更高层的代码处理
PriorityQueue(非并发)
ConcurrentSkipListMap
替代同步的SortedMap
ConcurrentSkipListSet
替代同步的SortedSet
Java 5
Java 6
同步工具类
*应用场景
(1)确保某个计算在其需要的所有资源都被初始化后才能继续执行
(2)确保某个服务在其所依赖的所有其他服务都已经启动之后才启动
(3)等待知道某个操作的所有参与者都就绪再继续执行
CountDownLatch:可以使一个或多个线程等待一组事件发生
FutureTask
*应用场景
(1)用作异步任务使用,且可以使用get方法获取任务的结果
(2)用于表示一些时间较长的计算
使用Callable对象实例化FutureTask类
信号量(Semaphore)
用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量
管理者一组虚拟的许可。acquire获得许可(相当于P操作),release释放许可(相当于V操作)
(1)二值信号量可用作互斥体(mutex)
(2)实现资源池,例如数据库连接池
(3)使用信号量将任何一种容器变成有界阻塞容器
能够阻塞一组线程直到某个事件发生
栅栏和闭锁的区别
所有线程必须同时到达栅栏位置,才能继续执行
闭锁用于等待事件,而栅栏用于等待线程
栅栏可以重用
CyclicBarrier
可以让一定数量的参与线程反复地在栅栏位置汇集
应用场景在并行迭代算法中非常有用
Exchanger
这是一种两方栅栏,各方在栅栏位置上交换数据。
应用场景:当两方执行不对称的操作(读和取)
任务与执行策略之间的隐形耦合
线程饥饿死锁
运行时间较长的任务
设置线程池的大小
配置ThreadPoolExecutor
corePoolSize
核心线程数大小,当线程数= corePoolSize的时候,会把runnable放入workQueue中
如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”
keepAliveTime
保持存活时间,当线程数大于corePoolSize的空闲线程能保持的最大时间。
workQueue
保存任务的阻塞队列
如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务
threadFactory
创建线程的工厂
handler
是一个枚举,表示 keepAliveTime 的单位(有NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS,7个可选值
线程的创建与销毁
管理队列任务
AbortPolicy
DiscardPolicy
DiscardOldestPolicy
CallerRunsPolicy
在调用构造函数后再定制ThreadPoolExecutor
扩展 ThreadPoolExecutor
afterExecute(Runnable r, Throwable t)
beforeExecute(Thread t, Runnable r)
terminated
递归算法的并行化
构建并发应用程序
任务执行
在线程中执行任务
清晰的任务边界以及明确的任务执行策略
任务边界
大多数服务器以独立的客户请求为界
在每个请求中还可以发现可并行的部分
任务执行策略
在什么(What)线程中执行任务?
任务按照什么(What)顺序执行(FIFO、LIFO、优先级)?
有多少个(How Many)任务能并发执行?
在队列中有多少个(How Many)任务在等待执行?
如果系统由于过载而需要拒绝一个任务,那么应该选择哪一个(Which)任务?另外,如何(How)通知应用程序有任务被拒绝?
在执行一个任务之前或之后,应该进行什么(What)动作?
使用Exector框架
newFixedThreadPool(固定长度的线程池)
newCachedThreadPool(不限规模的线程池)
newSingleThreadPool(单线程线程池)
newScheduledThreadPool(带延迟/定时的固定长度线程池)
具体如何使用可以查看JDK文档
找出可利用的并行性
某些应用程序中存在比较明显的任务边界,而在其他一些程序中则需要进一步分析才能揭示出粒度更细的并行性
任务的取消和关闭
任务取消
停止基于线程的服务
处理非正常的线程终止
JVM关闭
线程池的定制化使用
任务和执行策略之间的隐性耦合
线程池的大小
配置ThreadPoolExecutor(自定义的线程池)
此处需要注意系统默认提供的线程池是如何配置的
扩展ThreadPoolExector
GUI应用程序探讨
活跃度(Liveness)、性能、测试
避免活跃性危险
锁顺序死锁
动态的锁顺序死锁
在协作对象之间发生的死锁
死锁的避免与诊断
支持定时的显示锁
通过线程转储信息来分析死锁
其他活跃性危险
要避免使用线程优先级,因为这会增加平台依赖性,并可能导致活跃性问题。在大多数并发应用程序中,都可以使用默认的线程优先级。
糟糕的响应性
如果由其他线程完成的工作都是后台任务,那么应该降低它们的优先级,从而提高前台程序的响应性。
要解决这种活锁问题,需要在重试机制中引入随机性(randomness)。为了避免这种情况发生,需要让它们分别等待一段随机的时间
性能与可伸缩性
运行速度(服务时间、延时)
处理能力(吞吐量、计算容量)
可伸缩性:当增加计算资源时,程序的处理能力变强
如何提升可伸缩性
Java并发程序中的串行,主要来自独占的资源锁
· 之前刚接触鱼刺的时候发了个
【鱼刺线程池,自动换IP,队列重试框架(https://bbs.125.la/forum.php?mod=viewthreadtid=14178530
发现热度还不错,大家还是挺喜欢多线程的操作。
常言道:授人以鱼不如授人以渔,鱼刺类模块一直感觉确实稳定好用,对于新手来说一些命令还是比较难理解的。但不知道为什么一直没有详细教程。
今天趁这次开源大赛曾个热度 讲一下鱼刺多线程应用中 线程池Ex的使用方法,废话不多说,直接开始吧。
注: 所讲内容都是自己使用中所得出来的经验,如果讲的不好或者不对得地方请大佬指导哈。
已经请示过作者了:
@Bints
首先我们需要下载并载入 教程以 鱼刺类_多线程应用5.43为主:鱼刺类_多线程应用v5.4.zip
(112.11 KB, 下载次数: 228)
我们先来看看“鱼刺类_线程池Ex”的命令:
几个参数先说一下:
1.初始栈大小
:可以理解为申请内存的空间的初始大小(个人是这么理解的)。必须是4KB的倍数且最小8KB(8192字节)所以最小为8*1024,不用担心太少,任何情况下Windows会根据需要动态延长堆栈的大小
2.是否在UI线程
:如果填写了真,那么在循环里面会加个"处理事件()"的命令来让消息循环 去队列处理窗口组件操作 防止执行的时候窗口卡死,(记得在哪里看到过线程中处理事件()是没有效果的。不太懂~~)
置_初始栈大小()
设置初始栈的大小,也可以在创建()的第五个参数中设置。此命令可以在线程池工作中设置。
置_空闲回收事件()
设置线程空闲回收的时间,也可以在创建()的第三个参数中设置,此命令可以在线程池工作中设置。
置_最大线程数()
设置最大线程数量,也可以在创建()的第二个参数中设置,此命令可以在线程池工作中设置。
:顾名思义 创建线程池。
投递任务()
,向线程池中投递一个可执行的函数子程序指针,和投递任务_int()基本一模一样,在内部自动转换成指针地址到整数(子程序指针)
投递任务_int()
向线程池中投递一个可执行的函数指针地址
等待任务动态()
:就是等待线程,到指定的等待时间才会往下执行,可以用 触发任务动态来取消等待。
触发任务动态()
.这个需要和等待任务动态一起用,也可以理解为 放弃等待中的任务 继续向下执行
暂停正在工作的线程,配合 事件_暂停() 使用效果最佳,后续会详解。
事件_暂停()
需要配合暂停命令。如果系统发出了暂停命令返回假 如果正常工作返回真,如果正在销毁的话也会返回假。
取消暂停。
取_队列任务数()
获取队列中的正在执行的线程数量。
取_空闲任务数()
获取队列中的空闲线程数量,严格控制线程数量可以用到此命令,后续会详解。
取_是否空闲()
获取线程池状态是否彻底空闲,也就是说任务是否全部执行完毕,可以作为后续投递任务完任务后的判断。
取_线程池容量()
获取线程池的可执行的最小线程数量
取_最大线程容量()
获取线程池中可执行的最大线程数量
取_执行线程数()
获取正在工作的线程数量
取_状态()
获取线程正在工作的状态,返回值为常量中的: #线程池_未启动 #线程池_正在工作,#线程池_正在销毁,#线程池_正在创建
下面开始实战,将会用到所有线程池Ex中的命令
首先载入模块后在程序集变量中创建一个线程池Ex。
创建一个按钮。在按钮事件中写入:要执行的任务数量为1000 线程数量为50
如果已知 执行数量为1000了 直接计次循环 写下去可能执行不够准确,因为不排除会投递失败的情况。所以我们:
如下图:只有在投递任务成功的时候 计次才会递增。 但是每次循环都会判断 递增的计次是否小于任务数量,
如果小于就继续执行,如果大于就说明投递的任务数量已经达到了目标任务数,就可以跳出循环了
上图中:投递任务()命令 传递了两个参数 一个是局_计次 一个是 0,
投递 局_计次 可以在任务函数中获取到 用处也比较大,比如可以用作超级列表框的索引。(前提是已经插入了)
等待任务动态().为什么要等待呢,又是时候你投递的是内存指针,投递进去后等待 任务函数将它获取到并释放完毕后触发任务动态就好了
这样看着没什么问题 是吧~~
内存方面的知识后续再说把 先掠过,只是这样演示这节只讲线程池Ex
但是如果我们模拟一下真是线程场景 加个延时()
如上图所示,如果有延时的话线程池投递完任务直接销毁 会导致任务被中断,或者放弃了
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术产生的原因。
线程池(thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
线程池:存放线程。创建和销毁线程会浪费很多内存和cpu。
享有全球盛誉的编程专家Jeffrey Richter,这位与Microsoft .NET开发团队合作长达8年时间的资深顾问,在本书中和读者分享他编程生涯中积累的所有丰富经验和心得,他的独到、睿智的见解,他的远见卓识,为开发人员构建健壮、可靠和具有良好响应能力的应用程序与组件奠定了良好的基础。
《CLR via C#(第3版) 》针对.NET Framework 4.0和多核编程进行了全面更新和修订,是帮助读者深入探索和掌握公共语言运行时、C#和.NET开发的重要参考,同时也是帮助开发人员构建任何一种应用程序(如Microsoft Silverlight、ASP.NET、Windows Prensentation Foundation、Web服务和控制台应用程序)的良师益友。
本书涵括以下主题:
· 构建、部署应用程序、组件和共享程序集,并对它们进行版本管理
· 理解基元类型、值类型和引用类型的行为,从而最高效地定义和使用它们
· 使用泛型和接口来定义可重用的算法
· 高效使用特定的CLR类型——委托、枚举、定制attribute、数组和字符串
· 理解垃圾回收器是如何管理内存资源的
· 使用线程池、任务、取消、计时器和异步I/O操作来设计响应性强、稳定性高和伸缩性大的解决方案
· 借助于异常处理来进行状态管理
· 使用CLR寄宿、AppDomain、程序集加载、反射和C#的dynamic类型来构造具有动态扩展能力的应用程序
本书作者作者Jeffrey Richter,.NET和Windows编程领域当之无愧的大师和权威,以著述清楚明了,行文流水,言简意赅著称,在国内具有相当高的知名度,他的著作之一《Windows核心编程(第5版)》屡获殊荣,在国内外都享有盛誉,在国内因年销量过万而获得中国书刊业发行协会“2009年度全行业畅销书品种”称号。
第1章 CLR的执行模型
1.1 将源代码编译成托管模块
1.2 将托管模块合并成程序集
1.3 加载公共语言运行时
1.4 执行程序集的代码
1.4.1 IL和验证
1.4.2 不安全的代码
1.5 本地代码生成器:NGen.exe
1.6 Framework类库
1.7 通用类型系统
1.8 公共语言规范
1.9 与非托管代码的互操作性
第2章 生成、打包、部署和管理应用程序及类型
2.1 .NET Framework部署目标
2.2 将类型生成到模块中
2.2.1 响应文件
2.3 元数据概述
2.4 将模块合并成程序集
2.4.1 使用Visual Studio IDE将程序集添加到项目中
2.4.2 使用程序集链接器
2.4.3 为程序集添加资源文件
2.5 程序集版本资源信息
2.5.1 版本号
2.6 语言文化
2.7 简单应用程序部署(私有部署的程序集)
2.8 简单管理控制(配置)
第3章 共享程序集和强命名程序集
3.1 两种程序集,两种部署
3.2 为程序集分配强名称
3.3 全局程序集缓存
3.4 在生成的程序集中引用一个强命名程序集
3.5 强命名程序集能防范篡改
3.6 延迟签名
3.7 私有部署强命名程序集
3.8 “运行时”如何解析类型引用
3.9 高级管理控制(配置)
3.9.1 发布者策略控制
第4章 类 型 基 础
4.1 所有类型都从System.Object派生
4.2 类型转换
4.2.1 使用C#的is和as操作符来转型
4.3 命名空间和程序集
4.4 运行时的相互联系
第5章 基元类型、引用类型和值类型
5.1 编程语言的基元类型
5.1.1 checked和unchecked基元类型操作
5.2 引用类型和值类型
5.3 值类型的装箱和拆箱
5.3.1 使用接口更改已装箱值类型中的字段(以及为什么不应该这样做)
5.3.2 对象相等性和同一性
5.4 对象哈希码
5.5 dynamic基元类型
第6章 类型和成员基础
6.1 类型的各种成员
6.2 类型的可见性
6.2.1 友元程序集
6.3 成员的可访问性
6.4 静态类
6.5 分部类、结构和接口
6.6 组件、多态和版本控制
6.6.1 CLR如何调用虚方法、属性和事件
6.6.2 合理使用类型的可见性和成员的可访问性
6.6.3 对类型进行版本控制时的虚方法的处理
第7章 常量和字段
7.1 常量
7.2 字段
第8章 方法
8.1 实例构造器和类(引用类型)
8.2 实例构造器和结构(值类型)
8.3 类型构造器
8.3.1 类型构造器的性能
8.4 操作符重载方法
8.4.1 操作符和编程语言互操作性
8.5 转换操作符方法
8.6 扩展方法
8.6.1 规则和原则
8.6.2 用扩展方法扩展各种类型
8.6.3 ExtensionAttribute
8.7 分部方法
8.7.1 规则和原则
第9章 参 数
9.1 可选参数和命名参数
9.1.1 规则和原则
9.1.2 DefaultParameterValueAttribute和OptionalAttribute
9.2 隐式类型的局部变量
9.3 以传引用的方式向方法传递参数
9.4 向方法传递可变数量的参数
9.5 参数和返回类型的指导原则
9.6 常量性
第10章 属性
10.1 无参属性
10.1.1 自动实现的属性
10.1.2 合理定义属性
10.1.3 对象和集合初始化器
10.1.4 匿名类型
10.1.5 System.Tuple类型
10.2 有参属性
10.3 调用属性访问器方法时的性能
10.4 属性访问器的可访问性
10.5 泛型属性访问器方法
第11章 事件
11.1 设计要公开事件的类型
11.1.1 第一步:定义类型来容纳所有需要发送给事件通知接收者的附加信息
11.1.2 第二步:定义事件成员
11.1.3 第三步:定义负责引发事件的方法来通知事件的登记对象
11.1.4 第四步:定义方法将输入转化为期望事件
11.2 编译器如何实现事件
11.3 设计侦听事件的类型
11.4 显式实现事件
第12章 泛型
12.1 Framework类库中的泛型
12.2 Wintellect的Power Collections库
12.3 泛型基础结构
12.3.1 开放和封闭类型
12.3.2 泛型类型和继承
12.3.3 泛型类型同一性
12.3.4 代码爆炸
12.4 泛型接口
12.5 泛型委托
12.6 委托和接口的逆变和协变泛型类型实参
12.7 泛型方法
12.7.1 泛型方法和类型推断
12.8 泛型和其他成员
12.9 可验证性和约束
12.9.1 主要约束
12.9.2 次要约束
12.9.3 构造器约束
12.9.4 其他可验证性问题
第13章 接口
13.1 类和接口继承
13.2 定义接口
13.3 继承接口
13.4 关于调用接口方法的更多探讨
13.5 隐式和显式接口方法实现(幕后发生的事情)
13.6 泛型接口
13.7 泛型和接口约束
13.8 实现多个具有相同方法名和签名的接口
13.9 用显式接口方法实现来增强编译时类型安全性
13.10 谨慎使用显式接口方法实现
13.11 设计:基类还是接口?
第14章 字符、字符串和文本处理
14.1 字符
14.2 System.String类型
14.2.1 构造字符串
14.2.2 字符串是不可变的
14.2.3 比较字符串
14.2.4 字符串留用
14.2.5 字符串池
14.2.6 检查字符串中的字符和文本元素
14.2.7 其他字符串操作
14.3 高效率构造字符串
14.3.1 构造StringBuilder对象
14.3.2 StringBuilder的成员
14.4 获取对象的字符串表示:ToString
14.4.1 指定具体的格式和语言文化
14.4.2 将多个对象格式成一个字符串
14.4.3 提供定制格式化器
14.5 解析字符串来获取对象:Parse
14.6 编码:字符和字节的相互转换
14.6.1 字符和字节流的编码和解码
14.6.2 Base-64字符串编码和解码
14.7 安全字符串
第15章 枚举类型和位标志
15.1 枚举类型
15.2 位标志
15.3 向枚举类型添加方法
第16章 数组
16.1 初始化数组元素
16.2 数组转型
16.3 所有数组都隐式派生自System.Array
16.4 所有数组都隐式实现IEnumerable,Icollection和IList
16.5 数组的传递和返回
16.6 创建下限非0的数组
16.7 数组的访问性能
16.8 不安全的数组访问和固定大小的数组
第17章 委托
17.1 初识委托
17.2 用委托回调静态方法
17.3 用委托回调实例方法
17.4 委托揭秘
17.5 用委托回调许多方法(委托链)
17.5.1 C#对委托链的支持
17.5.2 取得对委托链调用的更多控制
17.6 委托定义太多啦(泛型委托)
17.7 C#为委托提供的简化语法
17.7.1 简化语法1:不需要构造委托对象
17.7.2 简化语法2:不需要定义回调方法
17.7.3 简化语法3:局部变量不需要手动包装到类中即可传给回调方法
17.8 委托和反射
第18章 定制attribute
18.1 使用定制attribute
18.2 定义自己的attribute类
18.3 attribute的构造器和字段/属性的数据类型
18.4 检测定制attribute
18.5 两个attribute实例的相互匹配
18.6 检测定制attribute时不创建从Attribute派生的对象
18.7 条件attribute类
第19章 可空值类型
19.1 C#对可空值类型的支持
19.2 C#的空接合操作符
19.3 CLR对可空值类型的特殊支持
19.3.1 可空值类型的装箱
19.3.2 可空值类型的拆箱
19.3.3 通过可空值类型调用GetType
19.3.4 通过可空值类型调用接口方法
第20章 异常和状态管理
20.1 定义“异常”
20.2 异常处理机制
20.2.1 try块
20.2.2 catch块
20.2.3 finally块
20.3 System.Exception类
20.4 FCL定义的异常类
20.5 抛出异常
20.6 定义自己的异常类
20.7 用可靠性换取开发效率
20.8 指导原则和最佳实践
20.8.1 善用finally块
20.8.2 不要什么都捕捉
20.8.3 得体地从异常中恢复
20.8.4 发生不可恢复的异常时回滚部分完成的操作——维持状态
20.8.5 隐藏实现细节来维系契约
20.9 未处理的异常
20.10 对异常进行调试
20.11 异常处理的性能问题
20.12 约束执行区域(CER)
20.13 代码契约
第21章 自动内存管理(垃圾回收)
21.1 理解垃圾回收平台的基本工作原理
21.1.1 从托管堆分配资源
21.2 垃圾回收算法
21.3 垃圾回收与调试
21.4 使用终结操作来释放本地资源
21.4.1 使用CriticalFinalizerObject类型确保终结
21.4.2 SafeHandle类型及其派生类型
21.4.3 使用SafeHandle类型与非托管代码进行互操作
21.5 对托管资源使用终结操作
21.6 什么会导致调用Finalize方法
21.7 终结揭秘
21.8 Dispose模式:强制对象清理资源
21.9 使用实现了Dispose模式的类型
21.10 C#的using语句
21.11 一个有趣的依赖性问题
21.12 手动监视和控制对象的生存期
21.13 对象复活
21.14 代
21.15 用于本地资源的其他垃圾回收功能
21.16 预测需求大量内存的操作能否成功
21.17 编程控制垃圾回收器
21.18 线程劫持
21.19 垃圾回收模式
21.20 大对象
21.21 监视垃圾回收
第22章 CLR寄宿和AppDomain
22.1 CLR寄宿
22.2 AppDomain
22.2.1 跨越AppDomain边界访问对象
22.3 卸载AppDomain
22.4 监视AppDomain
22.5 AppDomain FirstChance异常通知
22.6 宿主如何使用AppDomain
22.6.1 可执行应用程序
22.6.2 Microsoft Silverlight富Internet应用程序
22.6.3 Microsoft ASP.NET Web窗体和XML Web服务应用程序
22.6.4 Microsoft SQL Server
22.6.5 更多的用法只局限于你自己的想象力
22.7 高级宿主控制
22.7.1 使用托管代码管理CLR
22.7.2 编写健壮的宿主应用程序
22.7.3 宿主如何拿回它的线程
第23章 程序集加载和反射
23.1 程序集加载
23.2 使用反射构建动态可扩展应用程序
23.3 反射的性能
23.3.1 发现程序集中定义的类型
23.3.2 类型对象的准确含义
23.3.3 构建Exception派生类型的一个层次结构
23.3.4 构造类型的实例
23.4 设计支持加载项的应用程序
23.5 使用反射发现类型的成员
23.5.1 发现类型成员
23.5.2 BindingFlags:筛选返回的成员种类
23.5.3 发现类型的接口
23.5.4 调用类型的成员
23.5.5 一次绑定、多次调用
23.5.6 使用绑定句柄来减少进程的内存耗用
第24章 运行时序列化
24.1 序列化/反序列化快速入门
24.2 使类型可序列化
24.3 控制序列化和反序列化
24.4 格式化器如何序列化类型实例
24.5 控制序列化/反序列化的数据
24.5.1 如何在基类没有实现ISerializable的前提下定义一个实现它的类型
24.6 流上下文
24.7 将类型序列化为不同的类型以及将对象反序列化为不同的对象
24.8 序列化代理
24.8.1 代理选择器链
24.9 反序列化对象时重写程序集和/或类型
第25章 线程基础
25.1 Windows为什么要支持线程
25.2 线程开销
25.3 停止疯狂
25.4 CPU发展趋势
25.5 NUMA架构的机器
25.6 CLR线程和Windows线程
25.7 使用专用线程执行异步的计算限制操作
25.8 使用线程的理由
25.9 线程调度和优先级
25.10 前台线程和后台线程
25.11 继续学习
第26章 计算限制的异步操作
26.1 CLR线程池基础
26.2 执行简单的计算限制操作
26.3 执行上下文
26.4 协作式取消
26.5 任务
26.5.1 等待任务完成并获取它的结果
26.5.2 取消任务
26.5.3 一个任务完成时自动启动一个新任务
26.5.4 任务可以启动子任务
26.5.5 任务内部揭秘
26.5.6 任务工厂
26.5.7 任务调度器
26.6 Parallel的静态For,ForEach和Invoke方法
26.7 并行语言集成查询(PLINQ)
26.8 执行定时计算限制操作
26.8.1 太多的计时器,太少的时间
26.9 线程池如何管理线程
26.9.1 设置线程池限制
26.9.2 如何管理工作者线程
26.10 缓存线和伪共享
第27章 I/O限制的异步操作
27.1 Windows如何执行I/O操作
27.2 CLR的异步编程模型(APM)
27.3 AsyncEnumerator类
27.4 APM和异常
27.5 应用程序及其线程处理模型
27.6 异步实现服务器
27.7 APM和计算限制操作
27.8 APM的注意事项
27.8.1 在没有线程池的前提下使用APM
27.8.2 总是调用EndXxx方法,而且只调用一次
27.8.3 调用EndXxx方法时总是使用相同的对象
27.8.4 为BeginXxx和EndXxx方法使用ref,out和params实参
27.8.5 不能取消异步I/O限制操作
27.8.6 内存消耗
27.8.7 有的I/O操作必须同步完成
27.8.8 FileStream特有的问题
27.9 I/O请求优先级
27.10 将IAsyncResult APM转换为Task
27.11 基于事件的异步模式
27.11.1 将EAP转换为Task
27.11.2 APM和EAP的对比
27.12 编程模型的泥沼
第28章 基元线程同步构造
28.1 类库和线程安全
28.2 基元用户模式和内核模式构造
28.3 用户模式构造
28.3.1 易失构造
28.3.2 互锁构造
28.3.3 实现简单的Spin Lock
28.3.4 Interlocked Anything模式
28.4 内核模式构造
28.4.1 Event构造
28.4.2 Semaphore构造
28.4.3 Mutex构造
28.4.4 在一个内核构造可用时调用一个方法
第29章 混合线程同步构造
29.1 一个简单的混合锁
29.2 自旋、线程所有权和递归
29.3 混合构造的大杂烩
29.3.1 ManualResetEventSlim和SemaphoreSlim类
29.3.2 Monitor类和同步块
29.3.3 ReaderWriterLockSlim类
29.3.4 OneManyLock类
29.3.5 CountdownEvent类
29.3.6 Barrier类
29.3.7 线程同步构造小结
29.4 著名的双检锁技术
29.5 条件变量模式
29.6 用集合防止占有锁太长的时间
29.7 并发集合类
##TSContentCapsule
Content Capsule 使在 iOS 上执行异步任务更容易、更安全! 你可以:
编写和重用类来执行后台任务(称为“胶囊”)
编写不阻塞 UI 的内联网络代码
编写自动避免在主线程外调用 UI 的回调
配置线程池大小,以便在每种情况下进行更好的内存管理
使用过滤器丢弃排队的胶囊
包括示例: Pinterest 图像网格! (异步加载提要和所有图像)
###路线图
改进用户获取此工具的方式(也许使用 cocoapods?)。
编写更多丢弃过滤器(通过委托、线程关联等)
编写一个亲和系统,其中一个胶囊只能在线程池的一个子集中运行
编写更多可复用的常用胶囊(下载json、上传数据、下载进度、oauth、websocket)
构建一个探测类,记录当前活动/空闲线程的数量。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
* 测试得到
线程池中的当前活动线程
* @author keji
* @2013-4-7
在Java中,线程池的`getActiveCount()`方法是通过`ThreadPoolExecutor`类中的`getActiveCount()`方法实现的。这个方法会返回当前正在执行任务的线程数量,也就是活跃线程数。
在`ThreadPoolExecutor`类中,线程池中的任务是通过`execute()`方法提交的。当任务被提交时,如果线程池中有空闲线程,就会立即执行任务。如果没有空闲线程,则会将任务放入一个任务队列中,等待线程池中的线程空闲时再执行。
在`getActiveCount()`方法中,会调用`ThreadPoolExecutor`类中的`Worker`类的`isLocked()`方法来判断当前线程是否正在执行任务。`isLocked()`方法会通过判断当前线程是否持有`ReentrantLock`锁来判断线程是否正在工作。如果线程持有锁,则表示线程正在执行任务,否则表示线程空闲。
通过统计持有锁的线程数,就可以得到当前正在执行任务的线程数量,也就是线程池的活跃线程数。