随着摩尔定律的失效,现代计算机拥有越来越多的核心数,如何高效利用多核能力提升系统吞吐量、响应速度是非常关键的。在 Java 应用程序中实现安全、高效、可伸缩的并发编程是极其困难的,但仍有一些技巧可遵循
并发问题
所有的并发问题都可以归纳为如何协调对并发状态的访问。可变状态越少,越容易确保线程安全性
- 域如果不需要可变,尽量声明为 final 类型
- 不可变对象一定是线程安全的
- 封装可以有效管理复杂性。例如:可以将同步机制维护在对象内部
- 使用同一把锁保护同一个 不变性条件一组变量的值在某一时刻不会发生变化中的所有变量
- 对复合操作加锁
- 从多个线程中访问同一个未加锁的可变变量,程序肯定会出问题
- 在设计过程中考虑线程安全问题,并文档化
关于Java线程内容可以到这里了解 Java线程基础 和 并发与多线程
结构化
围绕“任务执行”来设计并发程序,可以简化开发过程,提供更好的并发维护
- 定义清晰的任务边界
- 把工作分解为一系列任务
- 任务封装为 FutureTask
- 将 FutureTask 提交给 Executor
Executor 框架将任务提交与执行策略解耦,支持不同类型的执行策略,使用 FutureTask 和 Executor 框架,可以帮助我们构建可取消的任务和服务
并发包
Java java.util.concurrent 中提供了非常多性能优秀的并发工具类,如:同步器、读写锁、原子变量等。在进行并发编程时,应该优先使用并发包中的工具
性能与可伸缩性
性能指标大概可以分为 运行速度 和 处理能力 两大类
- 运行速度:指定任务单元需要“多快”才能处理完成。例如:延迟时间、响应时间
- 处理能力:一定的计算资源下能完成“多少”工作。例如:吞吐量、可伸缩性
对服务器应用来说,“多少” 比 “多快” 更重要。在并发编程中,独占方式的资源锁是对可伸缩性的最主要威胁,因此可以通过减少锁的持有时间,降低锁的粒度,以及采用非独占锁(如:ReadWriteLock )或非阻塞锁(如:AtomicLong、ReentrantLock 等)来代替独占锁
可伸缩性是指:当增加计算资源时(CPU、内存、存储容量或I/O带宽),程序的吞吐量或者处理能力相应地提升
Amdahl’s Law
Amdahl 定律描述的是:在增加计算资源的情况下,程序在理论上能够实现最高加速比,这个值取决于程序中可并行组件与串行组件所占的比重。最高加速比的公式为:
$$\begin{equation*}
Speedup<=\frac{1}{F+\frac{(1-F)}{N}}
\end{equation*}$$
F:必须被执行的串行部分
1-F:并行执行部分
N:处理核心数
Speedup:问题规模不变的情况下,提升处理器并行能力后能提升多少系统性能
参考
I never had a family. I wanted one though. - Léon