Java - 不可变数组线程安全
我有一个关于 Java 内存模型的问题。这是一个提出问题的简单类:
public class ImmutableIntArray {
private final int[] array;
public ImmutableIntArray() {
array = new int[10];
for (int i = 0; i < 10; i++) {
array[i] = i;
}
}
// Will always return the correct value?
public int get(int index) {
return array[index];
}
}
据我所知,JMM 保证最终字段的值在构造后对其他线程可见。但我想确保其他线程在构造后会看到数组中存储的最新版本的数据。
当然,上面的代码只是一个简单的示例来说明问题,实际上我想为直接字节缓冲区实现一个简单的缓存,并且我不想依赖某些 Collection 类。目前我正在使用 ReentrantReadWriteLock 来确保正确的行为,但如果可能的话我希望避免它。
I have a question regarding the Java Memory Model. Here is a simple class presenting the problem:
public class ImmutableIntArray {
private final int[] array;
public ImmutableIntArray() {
array = new int[10];
for (int i = 0; i < 10; i++) {
array[i] = i;
}
}
// Will always return the correct value?
public int get(int index) {
return array[index];
}
}
As far as I know the JMM guarantees that the value of final fields will be visible to other threads after construction. But I want to ensure that other threads will see the most recent version of data stored in the array after construction.
Of course the code above is just a simple example presenting the problem, actually I want to implement a simple cache for direct byte buffers and I wouldn't like to rely on some Collection classes. Currently I am using a ReentrantReadWriteLock to ensure the correct behaviour but I would like avoid it if it is possible.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
在这个例子中,一切都会好起来的(嗯,让我们暂停一下判断)。当谈到线程安全时,不变性是一种美味——如果一个值不能改变,大多数并发问题就不再是一个问题。
Amir 提到
volatile
这通常很有用 - 但构造函数也有类似的语义 < code>final 确保可见性的变量。请参阅 JLS 条款 17.5详细信息 - 本质上,构造函数在最终变量的写入和任何后续读取之间形成了发生之前关系。编辑:因此,您在构造函数中设置了对数组的
values引用,此时它在所有线程中都是可见的,然后它就不会改变。所以我们知道所有其他线程都会看到相同的数组。但是数组的内容呢?就目前情况而言,数组元素在波动性方面没有任何特殊语义,它们就像您自己声明了一个类一样:
所以 - 如果我们可以做一些事情来建立<,另一个线程只会看到这些变量em>发生在之前的关系。如果我的理解是正确的,这只需要对您的原始代码进行很小的更改。
我们已经知道数组引用的设置发生在构造函数结束之前。始终正确的另一点是,一个线程中的操作发生在同一线程中的后续操作之前。因此,我们可以通过先设置数组字段,然后分配最终字段来组合这些,从而获得可见性的传递保证。这当然需要一个临时变量:
我认为这保证是安全的,因为我们已经改变了看似不相关的分配和填充顺序。
但同样,我可能还遗漏了其他一些东西,这意味着并发保证并不像希望的那么强大。在我看来,这个问题是一个很好的例子,说明了为什么编写防弹多线程代码很棘手,即使您认为自己正在做一些非常简单的事情,以及如何需要大量的思考和谨慎(然后是错误修复)才能正确。
In this example, everything
will be fine(hmm, let's suspend judgement a bit). Immutability is ambrosia when it comes to thread-safety - if a value cannot change, the majority of concurrency problems are immediately no longer a concern.Amir mentioned
volatile
which is generally useful - but the constructor also has similar semantics forfinal
variables that ensure visibility. See JLS clause 17.5 for details - essentially the constructor forms a happens-before relationship between the write to the final variables and any subsequent reads.EDIT: So you set the
valuesreference to the array in the constructor, it's visible across all threads at that point, and then it doesn't change. So we know all other threads will see the same array. But what about the array's contents?As it stands, array elements don't have any special semantics with regard to volatility, they're as if you just declared a class yourself something like:
So - another thread will only see these variables if we can do something to establish the happens-before relationship. And if my understanding is correct this requires but a small change to your original code.
We already know that the setting of the array reference happens-before the end of the constructor. An additional point which is always true, is that actions in one thread happen-before later actions in that same thread. So we can combine these by setting the array fields first, and then assigning the final field, so as to get this transitive guarantee of visibility. This will of course require a temporary variable:
I think this is guaranteed to be safe, now that we've switched the seemingly irrelevant order of assignment and population.
But again, there might be something else I've missed which means the concurrency guarantees aren't as robust as hoped. This question is to my mind an excellent example of why writing bulletproof multithreaded code is tricky, even when you think you're doing something very simple, and how it takes a lot of thought and caution (and then bugfixes) to get right.
你的例子不太正确。为了获得最终的现场保证,您需要:
your example is not quite right. in order to get the final field assurance, you need:
我确实认为数组的语义与对象的最终引用相同。规范规定
它还说
http://java.sun.com/docs/books /jls/third_edition/html/memory.html#17.5
I do think that you're afforded the same semantics with an array as with a final reference to an object. The spec states
It also says
http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.5
我认为你的数组更改将通过 ImmutableIntArray 可见。
根据我对 JLS 的阅读,[freeze] 操作应该在构造函数退出时发生。
我认为使用临时数组是没有用的:
为了获得最终的字段保证,我们需要在构造函数退出之前的某个地方使用 [freeze]:
无论如何,[freeze] 都会打开大门以重新排序其上方的指令,因此我们将得到相同的东西:
[freeze] 的实现至少包含一个 [StoreStore]。此 [StoreStore] 屏障必须在发布构造的实例之前发出。
来自JSR-133 Cookbook:
我认为这是由
(JSR-133 食谱):
因此,在所有其他构造函数存储完成之前,我们无法将其存储在sharedRef中。
您可以通过以下方式搜索:(JSR133规格)。
I think your array changes will be visible with your ImmutableIntArray.
From my reading on JLS the [freeze] action should take place when constructor exits.
The use of a temp array I think is useless:
To obtain final field guaranties we will need a [freeze] somewhere before constructor exit:
Anyway, [freeze] leaves the gates open to reorder instructions above it, so we will have the same thing:
[freeze] is implemented to contain at minimum a [StoreStore]. This [StoreStore] barrier must be issued before the moment when instance constructed is published.
From JSR-133 Cookbook:
And I think this is done by
(JSR-133 Cookbook):
So we can't store in sharedRef before all other contructor stores are done.
You can search by: "Transitive guarantees from final fields" in (JSR133 spec).