【有书共读】《Java并发编程实战》第十二章+第十三章
第十二章 并发程序的测试
- 并发非常高内需测试的挑战是:潜在错误的发生并不具有确定性,而是随机的,要在测试中将这些故障暴露出来,就需要比普通串行程序测试覆盖更广的范围并执行更长的时间。
- 性能测试可以通过多个方面来衡量,包括吞吐量、响应性、可伸缩性。
12.1 正确性测试
- 基本的单元测试,找出与并发性无关的问题。
- 对阻塞操作的测试,如果某方法需要在某些特定条件下阻塞,测试该行为时只有当线程不在继续执行时才是成功的。
- 安全性测试,最关键的问题在于,要找出容易检查的属性,在发生错误的情况下极有可能失败,同时又不会使得错误检查代码人为地限制并发性,理想情况是,在测试属性中不需要任何同步机制。
- 资源管理的测试,判断类中是否发生资源泄漏。
- 使用回调,回调函数的执行通常是在对象生命周期的一些已知位置上,并且在这些位置上非常适合判断不变性条件是否被破坏。
- 产生更多的交替操作,并发代码中大多数错误都是低概率事件,测试并发错误是需要反复地执行许多次。但有些方法可以提高发现这些错误的概率,比如在访问共享状态的操作中,使用Thread,yield将产生更多的上下文切换。
12.2性能测试
- 性能测试第一个目标是衡量典型测试用例中的端到端性能。
- 第二个目标是根据经验值来调整各种不同的限制,例如线程数量、缓存容量等。
12.3避免性能测试的陷阱
12.3.1 垃圾回收
- 垃圾回收的执行时序是无法预测的,可能在任何时刻运行,可能在最终测试的每次迭代时间上带来很大的影响。
- 防止垃圾回收带来的偏差,第一种策略时确保垃圾回收操作在测试运行的整个期间都不会执行,第二种是确保垃圾回收操作在测试期间执行多次,能充分反映出运行期间的内存分配与垃圾回收等开销。通常第二种更好。
12.3.2动态编译
- 编译的执行时机是无法预测的,只有在所有代码都编译完成后才应该统计测试运行时间,代码还可能反编译或重新编译。
- 两种方式防止动态编译带来的偏差,第一种是使程雪运行足够长的时间(数分钟),这样编译过程都知识总运行时间的很小一部分;第二种是使代码预先运行一段时间并且不测试这段时间内的代码性能,这样在开始计时前代码就已经完全编译了。
12.3.3代码路径的不真实采样
- JVM不会对并行的代码优化,应该讲单线程的性能测试与多线程的结合在一起。
12.3.4不真实的竞争程度
- 并发的应用程序可以交替执行两种不同类型的工作:访问共享数据以及执行线程本地计算,在应用程序中将出现不同程度的竞争,并表现出不同的性能与可伸缩性。在并发性能测试中应该尽量模拟典型应用程序中的线程本地计算量以及并发协调开销。
12.3.5无用代码的消除
- 要编写有效的性能测试程序,就需要告诉优化器不要讲基准测试当作代码而优化掉。这就要求在程序中对每个计算结果都要通过某种方式来使用,这种方式不需要同步或者大量的计算。
12.4 其他的测试方法
- 代码审查
- 静态分析工具,包括不一致的同步、调用Thread.run,未释放的锁,空的同步块,双重检查加锁,在构造函数中启动一个线程、通知错误、条件等待中的错误、对Lock和Condition的使用、在休眠或者等待的同时持有一个锁、自旋循环。
- 面向方面的测试技术
- 分析与监测工具
第十三章 显式锁
13.1Lock与ReentrantLock
- Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁的和解锁的方法都是显式的。
- ReentrantLock实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。
- ReentrantLock不能完全替代synchronized,程序的执行控制离开被保护的代码块时,不会自动清除锁。
13.1.1轮询锁与定时锁
- 可定时与可轮询的锁获取模式是通过tryLock方法实现的,具有更完善的错误恢复机制,可以避免死锁的发生。
13.1.2可中断的锁获取操作
- 可中断的锁获取操作的标准结构比普通的锁获取操作略微复杂一些,因为需要两个try块.
13.1.3非块结构的加锁
13.2性能考虑因素
- 性能是一个不断变化的指标,如果昨天的测试基准中发现X比Y更快,那么今天就可能已经过时了。
13.3 公平性
- ReentrantLock的构造函数可以选择公平性。
- 公平锁中,如果另一个线程持有这个锁或者有其他线程在队列中等待这个锁,那么新发出请求的线程将被放入队列中。在非公平锁中,只有当锁被某个线程持有时,新发出的请求线程才会被放入队列中。
- 激烈竞争的情况下,非公平锁的性能高于公平锁的一个原因是,在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。
- synchronized是非公平的,但大多数情况下在锁实现上实现统计上的公平性保证就足够了。
13.4synchronized与ReentrantLock之间的进行选择
- synchronized无法满足需求的情况下,ReentrantLock可以作为一种高级工具,功能包括:可定时的、可轮询的与可中断的锁获取操作、公平队列以及非块结构的锁。
13.5读写锁
- ReadWriteLock的加锁策略中,允许多个读操作同时进行,但每次只允许一个写操作。
- 可选的实现包括:释放优先、读线程插队、重入性、降级、升级。
#Java#