写时复制被认为是并发方案中的良好实践之一。但是,我不清楚它与写方法上的简单锁/同步有何不同。有人可以帮忙解释一下吗?
写时复制:
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)”后的读取动作将获得更新后的值。
那我们为什么要推广写时复制?为什么我们在写时必须“复制”?
答案 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的锁得到了改进,因此差异很小,但是在极端情况下不必获取锁/不必在线程之间进行协调会导致更高的吞吐量等。