Litong's Blog

Work to become, not to acquire.

第13章 并发编程

“对象是过程的抽象。线程是调度的抽象。”

编写整洁的并发程序很难——非常难。

并发是一种解耦策略。它帮助我们把做什么和何时做分解开,即分离了目的和时机。

解耦目的与时机能明显改进应用程序的吞吐量和结构。

从结构的角度来看,应用程序看起来更像是许多太协同工作的额计算机,而不是一个大循环。

有些系统对相应时间和吞吐量有要求,需要手工编写并发解决方案。

并发有时能改进性能,但只在多个线程或处理器之间能分享大量等待时间的时候管用。

并发算法的设计有可能与单线程系统的设计极不相同。目的与时机的解耦往往对系统结构产生巨大影响。

你最好了解容器在做什么,了解如何对付并发更新、死锁等问题。

正确的并发是复杂的,即便对于简单的问题也是如此。

并发缺陷并非总能重现,常被看做偶发事件而忽略。

并发常常需要对设计策略的根本性修改。

建议:分离并发相关代码与其他代码。

建议:谨记数据封装;严格限制对可能被共享的数据的访问。

使用数据副本,避免共享数据。

建议:尝试将数据分解到可被独立线程操作的独立子集。

建议:检读可用的类库。

了解执行模型:

建议:学习这些基础算法,理解其解决方案。

同步方法之间的依赖会导致并发代码中的狡猾缺陷。

建议:避免使用一个共享对象的多个方法。

有时必须使用一个共享对象的多个方法,此时有3种写代码的手段:

锁是昂贵的,因为它们带来了延迟和额外开销。

临界区应该被保护起来。所以,应该尽可能少地设计临界区。

建议:尽可能减小同步区域。

编写永远运行的系统,与编写运行一段时间后平静地关闭的系统是两码事。

平静关闭很难做到。
常见问题与死锁有关,线程一直等待永远不会到来的信号。

证明代码的正确性不切实际。

测试并不能保证正确性。
然而,好的测试却能尽量降低风险。

建议:编写有潜力暴露问题的测试,在不同的编程配置、系统配置和负载条件下频繁运行。

运行多于处理器数量的线程;
在不同平台上运行;
调整代码并强迫错误发生;

多数开发者缺乏有关线程如何与其他代码互动的直觉。

建议:不要将系统错误归咎于偶发事件。

先确保线程之外的代码可工作。

建议:不要同时追踪非线程缺陷和线程缺陷。先确保代码在线程之外可工作。

建议:编写可插拔的线程代码,这样就能在不同的配置环境下运行。

任务交换越频繁,越有可能找到错过临界区或导致死锁的代码。

不同操作系统有着不同线程策略,这影响了代码的执行。

建议:尽早并经常地在所有目标平台上运行线程代码。

有问题的代码,最好尽早、尽可能多地通不过测试。

第一要诀是遵循单一权责原则。将系统切分为分离了线程相关代码和线程无关代码的POJO。

了解并发问题的可能原因:对共享数据的多线程操作,或使用了公共资源池。

学习类库,了解基本算法。理解类库提供的与基础算法类似的解决问题的特性。

学习如何扎到必须锁定的代码区域并锁定之。不要锁定不必要的代码。

只要采用了整洁的做法,做对的可能性就有翻天覆地的提高。