写时复制与直接锁定/写入时同步方法有何不同?

时间:2019-06-09 15:48:04

标签: java multithreading concurrency

写时复制被认为是并发方案中的良好实践之一。但是,我不清楚它与写方法上的简单锁/同步有何不同。有人可以帮忙解释一下吗?

写时复制:

    public V put(K key, V value) {
        synchronized (this) {
            Map<K, V> newMap = new HashMap<K, V>(internalMap);
            V val = newMap.put(key, value);
            internalMap = newMap;
            return val;
        }
    }

直接锁定/同步:

    public V put(K key, V value) {
        synchronized (this) {
            internalMap.put(key, value);
        }
    }

对于写线程,它们在上面的两个示例中互斥,相同。

对于读取线程,在“写时复制”中,运行“ internalMap = newMap”后的读取动作将获取更新后的值。并且在直接锁定中,运行“ internalMap.put(key,value)”后的读取动作将获得更新后的值。

那我们为什么要推广写时复制?为什么我们在写时必须“复制”?

2 个答案:

答案 0 :(得分:5)

此示例中的一个好处是,您可以获得写时复制的快照语义:对internalMap的每个引用都是不可变的,一旦获得,就不会再更改。当您有许多并发的读取操作遍历internalMap并且仅偶尔进行更新时,这可能会很有用。

答案 1 :(得分:2)

使用锁定和写时复制都可以(实际上)实现相同的功能。他们中的任何一个都没有天生比另一个更好。

通常,当有很多读取但很少写入时,写时复制性能会更好。这是因为平均而言,读取比使用锁便宜,而由于复制,写入更昂贵。当您进行大量写操作时,通常最好使用锁。

为什么写操作更昂贵可能很明显(您必须在每次写操作时复制整个映射,,)。读物便宜的原因如下:

internalMap

读取internalMap不需要获取锁(有关更多详细信息,请参见Difference between volatile and synchronized in Java)。一旦线程获得对curl -X GET "https://en.wikipedia.org/api/rest_v1/page/random/summary" 的引用,它们就可以继续处理该副本(例如,遍历条目),而无需与其他线程进行协调,因为可以确保它不会被突变。尽可能多的线程可以处理单个映射副本(快照)。

以类推进行解释,假设一位作者正在起草一篇文章,而他们中有几人担任事实检查员。使用锁时,其中只有一个可以处理草稿。使用写时复制,作者将一个不变的快照(副本)发布到某个地方,事实检查人员可以抓取并完成他们的工作-在工作时,他们可以根据需要读取快照(而不是每次都中断作者)忘记了文章的某些部分等)。

多年来,Java的锁得到了改进,因此差异很小,但是在极端情况下不必获取锁/不必在线程之间进行协调会导致更高的吞吐量等。