在renderRow
中的size()
方法的JavaDoc中,它声明:
“请记住,包括ConcurrentHashMap
,size
和isEmpty
在内的聚合状态方法的结果通常仅在地图未在其他线程中进行并发更新时才有用。”
我知道在大多数并发应用程序中,映射本质上都是移动目标,因此要保证containsValue
方法返回的值保证正确并不常见。但在我的情况下,我确实需要它是正确的(不是陈旧的值)。
因为我真的不想为每个size()
调用锁定整个表,我的问题是为什么size()
不只是将表的大小存储在ConcurrentHashMap
字段中,作为AtomicInteger
或put()
操作的一部分进行更新?如果确实如此,那么写入size字段将始终是原子的(由于remove()
将其值存储在volatile字段中),那么来自size字段的任何读取都将返回最近更新的版本该领域......
或者我在这里遗漏了什么?
答案 0 :(得分:4)
是的,你错过了一些东西。想象一下10个线程执行更新,第11个线程定期确定地图的大小。
第11个线程读取的数字完全取决于其他10个线程的执行时间。使用原子整数对此没有帮助。
答案 1 :(得分:3)
作为ruediste explained,如果您正在进行并发更新,则大小信息毫无意义。在多次更新的情况下,您可以获得旧值,新值或任意中间值。
这与值是否存储在原子整数中的问题无关。但关于ConcurrentHashMap
的重点是允许并发更新。
“并发”意味着没有时间关系,也没有行动顺序。当一个线程试图插入一个值而另一个尝试删除一个值时,只有他们正在访问同一个键时才会有一个时间关系。在这种情况下,删除线程可以使用结果来判断插入是否发生在删除之前。在所有其他情况下,线程独立运作。
当同时查询大小时,您可能会获得旧大小,旧大小加一或旧大小减一(如果初始状态不为空)。如果您得到的值与旧尺寸不同,您可以使用它来推断插入或删除是否先发生,并得出错误的结论。使用containsKey
同时检测是否存在任一密钥的不同线程可能会感知到这些操作的不同顺序。这就是“没有订购”或“没有时间关系”的含义。
维护原子整数大小值不会改变任何东西。还有一点是线程修改了地图的内容,但还没有修改尺寸字段。此时可能存在任意数量的线程。但是强制所有线程更新单个原子整数会大大降低并发性。虽然原子整数比synchronized
块快,但它仍然是一种同步动作,它会受到高争用的影响。因此,如果不解决任何问题,它会降低性能。
答案 2 :(得分:2)
事实#1:更新单个size
字段可能会成为并发瓶颈,无论是volatile int
,AtomicInteger
还是其他。
事实#2:在ConcurrentHashMap
的绝大多数用例中,size()
方法对应用程序没用,因为它只告诉您特定时刻的大小。 (如果那样.javadoc实际上没有指定size()
在并发更新时将返回的内容。)
如果我们假设size()
不太可能被调用,那么在调用它时,在更新操作中引入潜在的并发瓶颈对我来说似乎是个坏主意。我猜测作者(Doug Lea)有类似的想法。