第一章 并发设计原理
1.6 设计并发算法的提示和技巧
正确识别独立任务:各任务之间不存在依赖性,没有顺序要求,可以无序执行
在尽可能高的层面上实施并发处理:例如执行器或 Fork/Join 框架 代替 Thread 类或 Lock 类来控制线程的创建和同步
考虑伸缩性:可以动态获取系统的核心数来评估创建多少线程
int numThreads = Runtime.getRuntime().availableProcessors();
使用线程安全API:使用 Concurrent 相关线程安全类代替非线程安全集合类
绝不要假定执行顺序
在静态和共享场合尽可能使用局部线程变量:使用 ThreadLocal 类来声明该类各属性,每个线 程都访问其自己的副本。
寻找更易于并行处理的算法版本
尽可能使用不可变对象
通过对锁排序来避免死锁
使用原子变量代替同步
占有锁的时间尽可能短
谨慎使用延迟初始化
避免在临界段中使用阻塞操作
第二章 使用基本元素:Thread 和 Runnable
2.1 Java 中的线程
- 特征和状态
- 守护线程 & 非守护线程
- 线程的可能状态
- Thread类和Runnable接口
第三章 管理大量线程:执行器
3.1 执行器简介
使用 Thread类 和 Runnable接口 可以创建并管理 Thread 对象,并且实现线程间的同步机制。然而,这也会带来一些问题,尤其对那些具有大量并发任务的应用程序来说更是如此。如果线程太多,就会降低应用程序性能,甚至会使整个系统中断运行。Java 5 引入了执行器框架解决这些问题,并且提供了一个高效的解决方案,相对于传统并发机制而言,该解决方案更便于编程人员使用。
3.1.1 执行器的基本特征
执行器的主要特征如下:
不需要手动创建任何 Thread 对象。
如果要执行一个并发任务,只需要创建一个执行该任务(例如一个实现 Runnable 接口的类)的实例并且将其发送给执行器。执行器会管理执行该任务的线程。
执行器通过复用线程来缩减线程创建带来的开销。
在内部,执行器管理着一个线程池,其中的线程称为工作线程(worker-thread)。如果向执行器发送任务而且存在某一空闲的工作线程,那么执行器就会使用该线程执行任务。
使用执行器控制资源很容易。
可以限制执行器工作线程的最大数目。如果发送的任务数多于工作线程数,那么执行器就会将任务存入一个队列。当工作线程完成某个任务的执行后,将从队列中调取另一个任务继续执行。
必须以显式方式结束执行器的执行。
你必须以显式方式结束执行器的执行,必须告诉执行器完成执行之后终止所创建的线程。如若不然,执行器则不会结束执行,这样应用程序也不会结束。
3.1.2 执行器框架的基本组件
执行器框架中含有各种接口和类,它们可实现执行器提供的全部功能。该框架的基本组件如下:
Executor接口
这是 Executor 框架的基本接口。它仅定义了一个方法,即允许编程人员向执行器发送一个 Runnable 对象。
ExecutorService 接口
该接口扩展了 Executor 接口并且包括更多方法,增加了该框架的功能,例如以下所述。
- 执行可返回结果的任务:Runnable 接口提供的 run()方法并不会返回结果,但是借用执行器,任务可以返回结果。
- 通过单个方法调用执行一个任务列表。
- 结束执行器的执行并且等待其终止。
ThreadPoolExecutor 类
该类实现了 Executor 接口和 ExecutorService 接口。此外, 它还包含一些其他获取执行器状态(工作线程的数量、已执行任务的数量等)的方法、确定 执行器参数(工作线程的最小和最大数目、空闲线程等待新任务的时间等)的方法,以及支 持编程人员扩展和调整其功能的方法。
Executors 类
该类为创建 Executor 对象和其他相关类提供了实用方法。
其他
评价
- 优点:
- 体系结构清晰完整,从 thread 到 执行器、从 Fork/Join 到 并发设计模式,算是掌握了并发体系的总体面貌
- 缺点:
- 理论基础讲解内容太少,略显干涩,介绍完概念就直接上代码,比较突兀
- 代码范例过于复杂,各个概念用的各个不同的范例,需要额外理解范例的需求和实现,耗费精力
参考资料
文档信息
- 本文作者:Bob.Zhu
- 本文链接:https://adolphor.github.io/2020/09/15/mastering-concurrency-programming-with-java9-2th-edition/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)