与其尝试推出自己的解决方案,不为何使用ConcurrentHashMap作为您的集合,而只是将所有值设置为某个标准值?(像
Boolean.TRUE这样的常量会很好用。)
我认为这种实现方式在许多读者很少的情况下效果很好。甚至还有一个构造函数,可让您设置预期的“并发级别”。
更新: Veer建议使用
Collections.newSetFromMap实用程序方法将ConcurrentHashMap转换为Set。由于该方法令人
Map<E,Boolean>怀疑,因此它会将所有值设置
Boolean.TRUE为幕后操作。
更新:解决海报的示例
那可能是我最终要解决的问题,但是我仍然对我的极简解决方案如何失败感到好奇。– MilesHampson
通过一些调整,您的极简解决方案将可以正常工作。我担心的是,尽管现在它很小,但将来可能会变得更加复杂。很难记住在进行线程安全操作时要假设的所有条件,尤其是如果您要在几周/几个月/几年后返回代码以进行看似微不足道的调整时。如果ConcurrentHashMap具有足够的性能来满足您的所有需求,那么为什么不使用它呢?所有令人讨厌的并发详细信息都被封装了起来,甚至从现在开始的6个月,您将很难将其弄乱!
您确实需要至少一项调整才能使当前解决方案生效。正如已经指出的那样,您可能应该将
volatile修饰符添加到
global的声明中。我不知道您是否具有C
/ C ++背景,但是当我得知
volatile
Java的语义实际上比C复杂得多时,我感到非常惊讶。如果您打算使用Java进行大量并发编程,那么熟悉Java内存模型的基础是一个好主意。如果您不引用
global某个
volatile引用,那么在
global尝试更新它之前,没有线程会看到该值的任何更改。
synchronized
块将刷新本地缓存并获取更新的参考值。
但是,即使添加了
volatile,仍然存在 很大的 问题。这是一个有两个线程的问题场景:
- 我们从空集或开始
global={}。线程A
和B
两者在其线程本地缓存的内存中均具有此值。 - 线程
A
获取获得synchronized
锁定global
并通过复制副本global
并将新密钥添加到集合中来开始更新。 - 当Thread
A
仍在synchronized
块内时,ThreadB
将其本地值读取global
到堆栈上并尝试进入该synchronized
块。由于线程A
当前位于监视器的线程B
块内。 - 线程
A
通过设置参考并退出监视器来完成更新,结果为global={1}。 - 线程
B
现在能够进入监视器并制作副本global={1}。 - 线程
A
决定进行另一次更新,读取其本地global
引用,然后尝试进入该synchronized
块。 由于线程B当前保持锁定状态,{}因此没有任何锁定{1},线程A
成功进入监视器! - 线程
A
还制作的副本以{1}进行更新。
现在,线程
A和
B都在
synchronized块内,并且它们具有相同的副本
global={1}集。 这意味着他们的更新之一将丢失!这种情况是由于您正在对存储在
synchronized块中要更新的引用中的对象进行同步而引起的。您应该始终非常小心地使用要同步的对象。您可以通过添加新变量来充当锁来解决此问题:
private volatile Collection global = new HashSet(); // start threading after thisprivate final Object globalLock = new Object(); // final reference used for synchronizationvoid allUpdatesGoThroughHere(Object exampleOperand) { // My hypothesis is that this prevents operations in the block being re-ordered synchronized(globalLock) { 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; }}这个错误非常阴险,其他答案都没有解决。
这些疯狂的并发细节使我建议使用已经调试过的java.util.concurrent库中的某些内容,而不是尝试自己编写一些内容。
我认为上述解决方案会奏效-但是将其再次拧紧有多容易?这样会容易得多:
private final Set<Object> global = Collections.newSetFromMap(new ConcurrentHashMap<Object,Boolean>());
由于是引用,因此
final您不必担心使用陈旧引用的线程,并且由于
ConcurrentHashMap内部处理了所有讨厌的内存模型问题,因此您不必担心监视器和内存屏障的所有讨厌的细节!



