在Java中,我可以依靠引用分配作为原子来实现写时复制吗?
如果我在多线程环境中有一个未同步的java集合,并且我不想强制该集合的读取器同步[1],那么我同步编写器并使用原子性引用分配的解决方案是否可行?比如:在Java中,我可以依靠引用分配作为原子来实现写时复制吗?,java,multithreading,synchronization,locking,copy-on-write,Java,Multithreading,Synchronization,Locking,Copy On Write,如果我在多线程环境中有一个未同步的java集合,并且我不想强制该集合的读取器同步[1],那么我同步编写器并使用原子性引用分配的解决方案是否可行?比如: private Collection global = new HashSet(); // start threading after this void allUpdatesGoThroughHere(Object exampleOperand) { // My hypothesis is that this prevents opera
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] 其原因是,与写操作相比,读操作花费的时间占了很大比例,再加上引入死锁的风险
编辑:在几个答案和评论中有很多好的信息,一些要点:
通过使
全局
易失性
替换同步
,您就可以在写入时复制了
虽然分配是原子的,但在其他线程中,它不随对引用对象的写入而排序。需要有一个before-before关系,您可以使用volatile
或同步读写
多个更新同时发生的问题是不同的-使用单个线程或任何您想在那里执行的操作
如果对读写操作都使用了synchronized
,那么这是正确的,但在需要切换的读操作中,性能可能不太好。ReadWriteLock
可能是合适的,但仍然会有写阻塞读
发布问题的另一种方法是使用final字段语义创建(理论上)可以安全地发布的对象
当然,也有并发集合可用。您可以使用
collections.synchronizedSet
方法吗?来自HashSet Javadoc
根据,
我们已经看到,增量表达式,例如c++
,并不描述原子操作。即使是非常简单的表达式也可以定义可以分解为其他动作的复杂动作。但是,您可以指定一些原子操作:
- 对于参考变量和大多数基本变量(除
和long
之外的所有类型),读写都是原子的double
- 对于声明的所有变量
(包括volatile
和long
变量),读写都是原子的double
全局
的更新值,也就是说,这里没有内存顺序保证
如果您通过synchronized
对全局
的所有访问使用隐式锁,那么您可以在这里建立一些内存一致性。。。但使用另一种方法可能更好
您似乎还希望global
中的集合保持不变。。。幸运的是,您可以使用它来强制执行此操作。例如,您可能应该执行以下操作
private volatile Collection global = Collections.unmodifiableSet(new HashSet());
。。。或者使用原子参考
private AtomicReference<Collection> global = new AtomicReference<>(Collections.unmodifiableSet(new HashSet()));
您应该知道,在这里进行复制是多余的,因为在内部为(Object elm:global)创建了一个迭代器,如下所示
final Iterator it = global.iterator();
while (it.hasNext()) {
Object elm = it.next();
}
因此,在阅读过程中,对于global
,不可能切换到完全不同的值
除此之外,我同意。。。在可能的情况下,您是否有任何理由在这里滚动您自己的数据结构?我想也许您正在处理一个较旧的Java,因为您使用的是原始类型,但问一下也无妨
您可以找到由或其同类(使用前者实现集
)提供的写时复制集合语义
另外,你有没有考虑过使用一种新的方法?它们保证像您在示例中所做的那样,使用for
循环将是一致的
类似地,迭代器和枚举返回反映哈希表在迭代器/枚举创建时或创建之后某个点的状态的元素
在内部,迭代器
用于在Iterable
上增强for
您可以通过utilizi在此基础上创建一个集
// ... 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
final Iterator it = global.iterator();
while (it.hasNext()) {
Object elm = it.next();
}
final Set<E> safeSet = Collections.newSetFromMap(new ConcurrentHashMap<E, Boolean>());
...
/* guaranteed to reflect the state of the set at read-time */
for (final E elem : safeSet) {
...
}
private volatile Collection global = new HashSet(); // start threading after this
private final Object globalLock = new Object(); // final reference used for synchronization
void 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;
}
}
private final Set<Object> global = Collections.newSetFromMap(new ConcurrentHashMap<Object,Boolean>());