在看到从另一个线程中删除之后的操作之前,在调用之前的一个线程中的操作是否ConcurrentMap.remove()保证发生?
有关放入集合中的对象的文档说明:
在将对象放入任何并发集合之前的线程中的操作发生在从另一个线程中的集合访问或移除该元素之后的操作之前.
示例代码:
{
final ConcurrentMap map = new ConcurrentHashMap();
map.put(1, new Object());
final int[] value = { 0 };
new Thread(() -> {
value[0]++;
value[0]++;
value[0]++;
value[0]++;
value[0]++;
map.remove(1); // A
}).start();
new Thread(() -> {
if (map.get(1) == null) { // B
System.out.println(value[0]); // expect 5
}
}).start();
}
Run Code Online (Sandbox Code Playgroud)
是一个在之前发生有关系乙?因此,如果该程序只打印5?
您已经发现这些并发工具的一个有趣而微妙的方面很容易被忽视。
\n\n首先,\xe2\x80\x99 不可能提供关于引用的删除和检索的一般保证null,因为后者仅证明不存在映射,但不能证明先前的删除,即线程可以读取映射\xe2 \x80\x99s 初始状态,在键具有映射之前,当然,这可以\xe2\x80\x99t 与映射\xe2\x80\x99s 构造之后发生的操作建立发生之前的关系。
另外,如果有多个线程删除相同的密钥,您可以\xe2\x80\x99t 假设发生在关系之前null之前,因为您不\xe2\x80\x99t 知道哪个删除已完成。此问题类似于两个线程插入相同值的情况,但后者可以通过仅执行可区分值的插入或遵循对正在执行的值对象执行所需修改的通常模式来在应用程序端修复。仅插入和查询检索到的对象。对于删除,没有这样的修复。
在您的特殊情况下,\xe2\x80\x99s操作与第二个线程的启动之间有一个发生之前的关系,因此如果第二个线程在查询键时遇到map.put(1, new Object())null1遇到,则 \xe2\x80\x99s 清楚地表明它见证了尽管唯一删除了您的代码,但规范并没有为这种特殊情况提供明确的保证。
相反, Java\xc2\xa08\xe2\x80\x99s的规范ConcurrentHashMap说:
\n\n\n检索反映了最近完成的更新操作在其开始时的结果。(更正式地说,给定密钥的更新操作带有发生之前与报告更新值的该键的任何(非空)检索具有
\n
明确排除null检索。
我认为,使用当前的 (Java\xc2\xa08)ConcurrentHashMap实现,您的代码可能会\xe2\x80\x99t 中断,因为它相当保守,因为它使用volatile语义执行对其内部支持数组的所有访问。但这只是目前的情况实现,如上所述,您的代码是一种特殊情况,并且可能会随着现实应用程序的每次更改而被破坏。
由于 ConcurrentHashMap 是一个线程安全集合,因此map.remove(1)如果该语句更改了映射,则该语句必须具有读屏障和写屏障。该表达式map.get(1)必须具有读取屏障,或者这些操作之一或两者都不是线程安全的。
实际上,Java 7 之前的 ConcurrentHashMap 使用分区锁,因此几乎每个操作都始终具有读/写屏障。
ConcurrentSkipListMap 不必使用锁,但要执行任何线程安全的写入操作,需要写入屏障。
这意味着您的测试应始终按预期进行。
| 归档时间: |
|
| 查看次数: |
215 次 |
| 最近记录: |