St.*_*rio 18 java arrays multithreading volatile
我有一个包含对数组的volatile引用的类:
private volatile Object[] objects = new Object[100];
Run Code Online (Sandbox Code Playgroud)
现在,我可以保证,只有一个线程(调用它writer
)可以写入数组.例如,
objects[10] = new Object();
Run Code Online (Sandbox Code Playgroud)
所有其他线程只读取线程写入的值writer
.
问题:我是否需要同步这样的读写以确保内存一致性?
我认为,是的,我应该.因为从性能角度来看,如果JVM在写入数组时提供某种内存一致性保证,那么它就没有用处.但我不确定.没有找到任何有用的文档.
Ale*_*you 14
你可以使用AtomicReferenceArray
:
final AtomicReferenceArray<Object> objects = new AtomicReferenceArray<>(100);
// writer
objects.set(10, new Object());
// reader
Object obj = objects.get(10);
Run Code Online (Sandbox Code Playgroud)
这将确保原子更新和发生 - 在读/写操作的一致性之前,就像每个数组项一样volatile
.
Coo*_*tri 13
Run Code Online (Sandbox Code Playgroud)private volatile Object[] objects = new Object[100];
你只能objects
参考volatile
这种方式.不是关联的数组实例内容.
问题:我是否需要同步这样的读写以确保内存一致性?
是.
如果JVM在写入数组时提供某种内存一致性保证,那么从性能角度来看它是没有用的
考虑使用集合CopyOnWriteArrayList
(或者你自己的数组包装器,Lock
在mutator和read方法中有一些实现).
Java平台也有Vector
(过时的设计有缺陷)和synchronized List
(很多场景都很慢),但我不建议使用它们.
PS: 来自@SashaSalauyou的另一个好主意
可以通过先发生关系来排序两个动作.如果一个动作发生在另一个动作之前,则第一个动作在第二个动作之前可见并在第二个之前被命
[...]
在对该字段的每次后续读取之前发生对
volatile
字段的写入.
在之前发生关系是相当强的.这意味着如果线程A写入volatile
变量,并且任何线程B稍后读取变量,则线程B保证看到volatile
变量本身的变化,以及在设置变量之前所做的每个其他变更线程A volatile
,包括是否是其他任何对象volatile
.
但是,这还不够!
元素分配objects[10] = new Object();
是不是写的变量objects
.它只是读取变量以确定它指向的数组,然后写入包含在位于内存中其他位置的数组对象中的不同变量.没有发生 - 在仅通过读取volatile
变量建立关系之前,因此代码不安全.
正如@DimitarDimitrov指出的那样,你可以通过对objects
变量进行虚拟写操作来解决这个问题.每一对操作 - objects = objects;
作者线程的重新分配以及foo = objects[x];
读取器线程的查找 - 定义更新的先发生关系,因此将"发布"作者线程对读取器线程所做的所有最新更改.这可行,但它需要纪律,而且不优雅.
但是有一个更微妙的问题:即使读者线程看到数组元素的更新值仍然不能保证它正确地看到该元素引用的对象的字段,因为以下顺序是可能的:
foo
.objects[x] = foo;
objects[x]
并查看对新对象的引用foo
(它可以执行,但不保证这样做,因为之前没有发生关系).objects = objects;
不幸的是,这并没有定义正式发生在之前的关系,因为volatile变量read(3)来自volatile变量write(4).虽然读者可以看到这objects[x]
是foo
偶然的对象,但这并不意味着安全发布的字段foo
,所以理论上读者可能会看到新的对象,但错误的值!为了解决这个问题,你使用这种技术的线程之间共享的对象需要有各个领域final
或volatile
或以其他方式同步.String
例如,如果对象都是s,那么你会没事的,但除此之外,用它来犯错也太容易了.(感谢@Holger指出这一点.)
以下是一些不太明显的替代品:
并发数组类AtomicReferenceArray
存在以提供其中每个元素都表现得像的数组volatile
.这更容易正确使用,因为它确保如果读者看到更新的数组元素值,它还可以正确地看到该元素引用的对象.
您可以在synchronized
块中包含对数组的所有访问,在某些共享对象上进行同步:
// writer
synchronized (aSharedObject) {
objects[x] = foo;
}
Run Code Online (Sandbox Code Playgroud)
// reader
synchronized (aSharedObject) {
bar = objects[x];
}
Run Code Online (Sandbox Code Playgroud)
就像volatile
,使用synchronized
创造一个发生在之前的关系.(在释放对象的同步锁之前,线程所做的一切都发生在任何其他线程获取同一对象的同步锁之前.)如果这样做,则不需要数组volatile
.
考虑一下阵列是否真的是你需要的.你还没有说出这些作者和读者线程的用途,但是如果你想要某种生产者 - 消费者队列,那么你真正需要的类是a BlockingQueue
或者Executor
.你应该查看Java并发类,看看其中一个是否已经完成了你需要的东西,因为如果有的话,它肯定比正确使用更容易volatile
.
是的,您需要同步访问易失性数组的元素.
其他人已经解决了你可能会如何使用CopyOnWriteArrayList
或AtomicReferenceArray
代替,所以我将转向一个稍微不同的方向.我还建议由JMM的一大贡献者Jeremy Manson 阅读Java中的Volatile Arrays.
现在,我可以保证只有一个线程(称为编写器)可以写入数组,如下所示:
是否可以提供单个作者保证与volatile
关键字无关.我认为你没有考虑到这一点,但我只是澄清,以便其他读者不会得到错误的印象(我认为有一个数据竞争双关语,可以用这句话).
所有其他线程只读取编写器线程写入的值.
是的,但是就像你的直觉正确引导你一样,这只适用于数组引用的值.这意味着除非您正在编写对volatile
变量的数组引用,否则您将无法获得volatile
写 - 读合同的写入部分.
这意味着要么你想要做的事情
objects[i] = newObj;
objects = objects;
Run Code Online (Sandbox Code Playgroud)
这在许多方面都是丑陋可怕的.或者你想在每次作家进行更新时发布一个全新的数组,例如
Object[] newObjects = new Object[100];
// populate values in newObjects, make sure that newObjects IS NOT published yet
// publish newObjects through the volatile variable
objects = newObjects;
Run Code Online (Sandbox Code Playgroud)
这不是一个非常常见的用例.
请注意,与设置不提供volatile
-write语义的数组元素不同,获取数组元素(with newObj = objects[i];
)确实提供了volatile
-read语义,因为您正在取消引用数组:)
因为如果JVM在写入数组时提供某种内存一致性保证,从性能角度来看是没有用的.但我不确定.
就像你提到的那样,确保volatile
语义所需的内存防护成本非常高,如果你在混合中添加错误共享,它就会变得更糟.
没有找到任何有用的文档.
您可以放心地假设volatile
数组引用的volatile
语义与非数组引用的语义完全相同,这一点都不奇怪,考虑到数组(甚至原始数组)仍然是对象.
归档时间: |
|
查看次数: |
1821 次 |
最近记录: |