Java Concurrency in Practice 第三部分 活跃性、性能与测试

避免活跃性危险
  • 如果在持有锁时调用某个外部方法,那么将出现活跃性问题,在这个外部方法中可能获取其他锁(这可能会产生死锁),或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁。
  • 如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用(Open Call)。可以通过这种方式调用来避免死锁。
  • 在程序中应尽量使用开放调用。与那些在持有锁时调用外部方法的程序相比,更易于依赖于开放调用的程序进行死锁分析。
  • 要避免使用线程优先级,因为这会增加平台依赖性,并可能导致活跃性问题,在大多数并发应用程序中,都可以使用默认的线程优先级。
  • 活跃性故障是一个非常严重的问题,因为当出现活跃性故障时,除了中止应用程序之外没有其他任何机制可以帮助从这种故障时恢复过来。最常见的活跃性故障就是 锁顺序死锁。在设计时应该避免产生锁顺序死锁,确保线程在获取多个锁时采用一致的顺序,最好的解决方法是在程序中使用使用开放调用。这将大大减少需要同时持有 多个锁的地方,也更容易发现这些地方。
性能与可伸缩性
  • 可伸缩性指的是:当增加计算资源时(例如CPU、内存、存储容量或I/O带宽),程序的吞吐两或者处理能力能相应地增加。
  • 避免不成熟的优化,首先程序正确,然后再提高运行速度–如果它还运行的不够快。
  • 以测试为基准,不要猜测。
  • 再所有并发程序中都包含了一些串行部分,如果你认为在你的程序中不存在串行部分,那么可以再仔细检查一遍。
  • 不要过度担心非竞争同步带来的开销。这个基本的机制已经非常了,并且JVM还能进行额外的优化以进一步降低或消除开销。因此,我们应该将优化重点放在那些发生锁竞争的地方。
  • 在并发程序中,对可伸缩性的最主要威胁就是独占方式的资源所。
  • 有三种方式可以降低锁的竞争程度:
    1. 减少锁的持有时间。
    2. 降低锁的请求频率。
    3. 使用带有协调机制的独占锁,这些机制允许更高的并发性。
  • 通常,对象分配操作的开销比同步的开销更低。
  • 并发程序性能,通常更多的重点放在吞吐量和可伸缩性上,而不是服务时间,Amdahl定律告诉我们,程序的可伸缩性取决与在所有代码中必须串行执行的代码比例,因为java程序串行操作的主要 来源是独占方式的资源锁,因此通常可以通过以下方式来提升可伸缩性:减少锁的持有时间,降低锁的粒度,以及采用非独占的锁或非阻塞锁来代替独占锁。
并发程序的测试
  • 在构建对并发类的安全性测试中,需要解决的关键问题在于,要找出那些容易检查的属性,这些属性在发生错误的情况下极有可能失败,同时又不会使得错误检查代码人为地限制并发性。 理想情况是,在测试属性中不需要任何同步机制。
  • 测试应该放在多处理器的系统上运行,从而进一步测试更多形式的交替运行。然而,CPU的数量越多并不一定会使测试越搞笑。要最大成都地检测出一些对执行时序敏感的数据竞争,那么测试中的 线程数量应该多于CPU数量,这样在任意时刻都会有一些线程在运行,而另一些被交换出去,从而可以检查线程间交替行为的可预测性。
  • 要编写有效的性能测试程序,就需要告诉优化器不要将基准测试当作无用代码而优化掉。这就要求在程序中对每个计算结果都要通过某种方式来使用,这种方式不需要同步或者大量的计算。