diff --git a/docs/book/Appendix-Low-Level-Concurrency.md b/docs/book/Appendix-Low-Level-Concurrency.md index 21e23571..d85e110d 100644 --- a/docs/book/Appendix-Low-Level-Concurrency.md +++ b/docs/book/Appendix-Low-Level-Concurrency.md @@ -1637,11 +1637,29 @@ public class PriorityBlockingQueueDemo { **Producer** 和 **Consumer** 通过 **PriorityBlockingQueue** 相互连接。因为阻塞队列的性质提供了所有必要的同步,因为阻塞队列的性质提供了所有必要的同步,请注意,显式同步是并不需要的 — 从队列中读取数据时,你不用考虑队列中是否有任何元素,因为队列在没有元素时将阻塞读取。 -### Lock-Free Collections +### 无锁集合 -#### The Copying Strategy +[集合](./12-Collections.md) 章节强调集合是基本的编程工具,这也要求包含并发性。因此,早期的集合比如 **Vector** 和 **Hashtable** 有许多使用 **synchronized** 机制的方法。当这些集合不是在多线程应用中使用时,这就导致了不可接收的开销。在 Java 1.2 版本中,新的集合库是非同步的,而给 **Collection** 类赋予了各种 **static** **synchronized** 修饰的方法来同步不同的集合类型。虽然这是一个改进,因为它让你可以选择是否对集合使用同步,但是开销仍然基于同步锁定。 Java 5 版本添加新的集合类型,专门用于增加线程安全性能,使用巧妙的技术来消除锁定。 -#### Compare-And-Swap (CAS) +无锁集合有一个有趣的特性:只要读取者仅能看到已完成修改的结果,对集合的修改就可以同时发生在读取发生时。这是通过一些策略实现的。为了让你了解它们是如何工作的,我们来看看其中的一些。 + +#### 复制策略 + +使用“复制”策略,修改是在数据结构一部分的单独副本(或有时是整个数据的副本)上进行的,并且在整个修改过程期间这个副本是不可见的。仅当修改完成时,修改后的结构才与“主”数据结构安全地交换,然后读取者才会看到修改。 + +在 **CopyOnWriteArrayList** ,写入操作会复制整个底层数组。保留原来的数组,以便在修改复制的数组时可以线程安全地进行读取。当修改完成后,原子操作会将其交换到新数组中,以便新的读取操作能够看到新数组内容。 **CopyOnWriteArrayList** 的其中一个好处是,当多个迭代器遍历和修改列表时,它不会抛出 **ConcurrentModificationException** 异常,因此你不用就像过去必须做的那样,编写特殊的代码来防止此类异常。 + +**CopyOnWriteArraySet** 使用 **CopyOnWriteArrayList** 来实现其无锁行为。 + +**ConcurrentHashMap** 和 **ConcurrentLinkedQueue** 使用类似的技术来允许并发读写,但是只复制和修改集合的一部分,而不是整个集合。然而,读取者仍然不会看到任何不完整的修改。**ConcurrentHashMap** **不会抛出concurrentmodificationexception** 异常。 + +#### 比较并交换 (CAS) + +在 比较并交换 (CAS) 中,你从内存中获取一个值,并在计算新值时保留原始值。然后使用 CAS 指令,它将原始值与当前内存中的值进行比较,如果这两个值是相等的,则将内存中的旧值替换为计算新值的结果,所有操作都在一个原子操作中完成。如果原始值比较失败,则不会进行交换,因为这意味着另一个线程同时修改了内存。在这种情况下,你的代码必须再次尝试,获取一个新的原始值并重复该操作。 + +如果内存仅轻量竞争,CAS操作几乎总是在没有重复尝试的情况下完成,因此它非常快。相反,**synchronized** 操作需要考虑每次获取和释放锁的成本,这要昂贵得多,而且没有额外的好处。随着内存竞争的增加,使用 CAS 的操作会变慢,因为它必须更频繁地重复自己的操作,但这是对更多资源竞争的动态响应。这确实是一种优雅的方法。 + +最重要的是,许多现代处理器的汇编语言中都有一条 CAS 指令,并且也被 JVM 中的 CAS 操作(例如 **Atomic** 类中的操作)所使用。CAS 指令在硬件层面中是原子性的,并且与你所期望的操作一样快。 ## 本章小结