【牛客有书共读】《Java并发编程实战》第十章+第十一章

第十章 避免活跃性危险

死锁

  • 当一组java线程发生死锁是,这些线程不能再使用,可能造成应用程序完全重启,或者某个特定的子系统停止,或者性能降低。恢复应用程序的唯一方式就是中止并重启,并希望不再发生。
  • 死锁造成的影响很少会立即显现,死锁一般在高负载的情况下出现。

锁顺序死锁

  • 如果所有线程以固定的顺序获得锁,那么程序中就不会出现顺序死锁问题。
  • 想要验证锁顺序的一致性,需要对程序中的加锁行为进行全局分析。

动态的锁顺序死锁

  • 在制定锁的顺序时,可以使用System.identifyHashCode方法。
  • Hash冲突是,可以使用“加时赛”锁,从而保证每次只有一个线程以未知的顺序获得锁,消除死锁的可能性。
  • 可以通过键值对对象进行排序,比如id号。

在协作对象之间发生的死锁

  • 如果在持有锁时调用某个外部方法,那么将出现活跃性问题。在这个外部方法中可能会获得其他锁,或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁。

开放调用

  • 如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用。
  • 在程序中应尽量使用开放调用,与那些在持有锁时调用外部方法的程序相比,更容易依赖于开放调用的程序进行死锁分析。

资源死锁

  • 多个线程在相同的资源集合上等待时,也会发生死锁。
  • 如果某些任务需要等待其他任务的结果,那么这些任务往往是产生线程饥饿死锁的主要来源,有界线程池/资源池与相互依赖的任务不能一起使用。

死锁的避免与诊断

  • 尽量减少潜在的加锁交互数量,将获取锁时需要遵循的协议写入正式文档并始终遵循这些协议。
  • 支持定时的锁
  • 通过线程转储信息来分析死锁

其他活跃性危险

饥饿

  • 要避免使用线程优先级,因为会增加平台依赖性,并可能导致活跃性问题,在大多数并发应用程序中,都可以使用默认的线程优先级。

糟糕的响应性

活锁

  • 活锁不会阻塞线程,但也不能继续执行,因为线程将不断重复执行相同的操作,而且总会失败。
  • 要解决活锁问题,需要在重试机制中引入随机性。

第十一章 性能与可伸缩性

  • 线程的最主要目的是提高程序的运行性能,但始终要把安全性放在第一位。

对性能的思考

  • 要通过并发来获得更好的性能,要做好两件事:更有效地利用现有处理资源,以及在出现新的处理资源时使程序尽可能地利用这些新资源。

性能与可伸缩性

  • 可伸缩性是指,当增加计算资源时,程序的吞吐量或者处理能力相应地增加。
  • 在进行可伸缩性调优时,其目的是设法将问题的计算并行化,从而能利用更多的计算资源来完成更多的工作。

评估各种性能权衡因素

  • 大多数优化措施都不成熟的原因之一,它们通常无法获得一组明确的需求。
  • 对性能的提升可能是并发错误的最大来源。
  • 以测试为基准,不要猜测。

Amdahl定律

  • 在增加计算资源的情况下,程序在理论上能实现最高加速比,这个值取决于程序中可并行组件与串行组件所占比重。
  • 在所有并发程序中都包含一些串行部分。

线程引入的开销

  • 对于为了提升性能而引入的线程,并行带来的性能提升必须超过并发导致的开销。

上下文切换

内存同步

  • 不要过度担心内存同步带来的开下,JVM能进行额外的优化以进一步降低或消除开销,应该将优化重点放在发生锁竞争的地方。

阻塞

减少锁的竞争

  • 在并发程序中,对可伸缩性的最主要威胁就是独占方式的资源锁。
  • 锁的请求频率和每次持有该锁的时间会影响锁上发生竞争的可能性。

缩小锁的范围(“快进快出”)

  • 在分解同步代码块时,仅当可以将一些大量的计算或阻塞操作从同步代码块中移除是,才应该考虑同步代码块的大小。

减小锁的粒度

  • 降低线程请求锁的频率,如果一个锁需要保护多个相互独立的状态变量,那么可以将这个锁分解为多个锁,并且每个锁只保护一个变量,从而提高可伸缩性,并最终降低每个锁被请求的频率。
  • 如果在锁上存在适中而不是激烈的竞争时,通过将一个锁分解为两个锁,能最大限度地提升性能。

锁分段

  • 在某些情况下,可以将锁分解技术进一步扩展为对一组独立对象上的锁进行分解,这种情况称为锁分段。
  • 锁分段一个劣势是,在采用单个锁来实现独占访问相比,要获取多个锁来实现独占访问将更加困难并且开销更高。

避免热点域

一些替代独占锁的方法

  • 使用一种友好的并发方法来管理共享状态,比如使用并发容器、续写所、不可变对象以及原子变量。

检测CPU利用率

  • CPU没有得到充分利用,通常原因有负载不充足、I/O密集、外部限制、锁竞争。

向对象池说“不”

  • 通常,对象分配操作的开销比同步的开销更低。

减少上下文切换的开销

  • 通过将I/O操作从处理请求的线程中分离出来,可以缩短处理请求的平均服务时间。
  • 阻塞和上下文奇幻同样会干扰线程的正常执行。

小结

  • 程序的可伸缩性取决于在所有代码中必须被串行执行的代码比例。

你可能感兴趣的:(【牛客有书共读】《Java并发编程实战》第十章+第十一章)