如果我在多线程环境中有一个不同步的 java 集合,并且我不想强制集合的读取器同步[1],那么我同步写入器并使用引用分配的原子性的解决方案是否可行?就像是:
private Collection global = new HashSet(); // start threading after this
void allUpdatesGoThroughHere(Object exampleOperand) {
// My hypothesis is that this prevents operations in the block being re-ordered
synchronized(global) {
Collection copy = new HashSet(global);
copy.remove(exampleOperand);
// Given my hypothesis, we should have a fully constructed object here. So a
// reader will either get the old or the new Collection, but never an
// inconsistent one.
global = copy;
}
}
// Do multithreaded reads here. All reads are done through a reference copy like:
// Collection copy = global;
// for (Object elm: copy) {...
// so the global reference being updated half way through should have no impact
在这些类型的情况下,滚动您自己的解决方案似乎经常失败,所以我有兴趣了解其他模式、集合或库,我可以使用这些模式、集合或库来防止创建对象并阻止我的数据消费者。
[1] 原因是与写入相比,读取花费的时间占很大比例,以及引入死锁的风险。
编辑:几个答案和评论中有很多很好的信息,一些要点:
- 我发布的代码中存在错误。在全局(一个命名错误的变量)上同步可能无法在交换后保护同步块。
- 您可以通过在类上进行同步来解决此问题(将同步关键字移动到方法中),但可能存在其他错误。一个更安全、更易于维护的解决方案是使用 java.util.concurrent 中的一些东西。
- 我发布的代码中没有“最终一致性保证”,确保读者看到作者更新的一种方法是使用 volatile 关键字。
- 回想起来,激发这个问题的一般问题是试图在 java 中使用锁定写入来实现无锁读取,但是我的(已解决的)问题是一个集合,这可能会让未来的读者感到不必要的困惑。因此,如果我发布的代码不明显,则通过一次允许一个作者对不受多个阅读器线程保护的“某个对象”执行编辑来工作。编辑的提交是通过原子操作完成的,因此读者只能获得编辑前或编辑后的“对象”。当/如果读取器线程获得更新时,它不能发生在读取中间,因为读取发生在“对象”的旧副本上。在 java 提供更好的并发支持之前,可能已经发现并证明以某种方式破坏了一个简单的解决方案。