最终字段对于线程安全是否真的有用? [英] Are final fields really useful regarding thread-safety?

查看:119
本文介绍了最终字段对于线程安全是否真的有用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经每天使用Java Memory Model工作多年了。我认为我对数据竞争的概念以及避免它们的不同方法(例如,同步块,易变变量等)有很好的理解。但是,仍然有一些我认为我完全不了解内存模型的东西,这是类的最终字段应该是线程安全的,没有任何进一步的同步。

I have been working on a daily basis with the Java Memory Model for some years now. I think I have a good understanding about the concept of data races and the different ways to avoid them (e.g, synchronized blocks, volatile variables, etc). However, there's still something that I don't think I fully understand about the memory model, which is the way that final fields of classes are supposed to be thread safe without any further synchronization.

所以根据规范,如果一个对象被正确初始化(也就是说,没有引用对象在其构造函数中以某种方式进行转义,使得引用可以被另一个线程看到),那么,在构造之后,任何看到对象的线程将保证看到对象的所有最终字段的引用(在它们构造时的状态),没有任何进一步的同步。

So according to the specification, if an object is properly initialized (that is, no reference to the object escapes in its constructor in such a way that the reference can be seen by another thread), then, after construction, any thread that sees the object will be guaranteed to see the references to all the final fields of the object (in the state they were when constructed), without any further synchronization.

特别是标准( http:/ /docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4 )说:


最终字段的使用模型很简单:为obj设置最终字段
在该对象的构造函数中;并且不要在对象的构造函数完成之前在另一个
线程可以看到的地方写入正在构造的对象的
引用。如果遵循此
,那么当另一个线程看到该对象时,该
线程将始终看到该
对象的最终字段的正确构造版本。它还会看到那些最终字段引用的任何对象或
数组的版本,这些字段至少与最终字段一样是最新的

The usage model for final fields is a simple one: Set the final fields for an object in that object's constructor; and do not write a reference to the object being constructed in a place where another thread can see it before the object's constructor is finished. If this is followed, then when the object is seen by another thread, that thread will always see the correctly constructed version of that object's final fields. It will also see versions of any object or array referenced by those final fields that are at least as up-to-date as the final fields are.

他们甚至给出以下示例:

They even give the following example:

class FinalFieldExample { 
    final int x;
    int y; 
    static FinalFieldExample f;

    public FinalFieldExample() {
        x = 3; 
        y = 4; 
    } 

    static void writer() {
        f = new FinalFieldExample();
    } 

    static void reader() {
        if (f != null) {
            int i = f.x;  // guaranteed to see 3  
            int j = f.y;  // could see 0
        } 
    } 
}

In哪个线程A应该运行reader(),线程B应该运行writer()。

In which a thread A is supposed to run "reader()", and a thread B is supposed to run "writer()".

到目前为止,这么好,显然。

So far, so good, apparently.

我主要担心的是......这在实践中真的有用吗?据我所知,为了使线程A(运行reader())看到对f的引用,我们必须使用一些同步机制,例如使f volatile,或者使用lock来同步访问F。如果我们不这样做,我们甚至不能保证reader()能够看到初始化的f,也就是说,由于我们没有同步访问f,读者可能会看到 null而不是由编写器线程构造的对象。此问题在 http:// www中说明.cs.umd.edu /~pugh / java / memoryModel / jsr-133-faq.html#finalWrong ,这是Java内存模型的主要参考之一[大胆强调我的]:

My main concern has to do with... is this really useful in practice? As far as I know, in order to make thread A (which is running "reader()") see the reference to "f", we must use some synchronization mechanism, such as making f volatile, or using locks to synchronize access to f. If we don't do so, we are not even guaranteed that "reader()" will be able to see an initialized "f", that is, since we have not synchronized access to "f", the reader will potentially see "null" instead of the object that was constructed by the writer thread. This issue is stated in http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#finalWrong , which is one of the main references for the Java Memory Model [bold emphasis mine]:


现在,在一个线程构造
不可变对象(即只包含最终字段的对象)之后,已经说过所有这些),
你想确保所有其他
线程都能正确看到它,你仍然通常需要使用同步。 没有
其他方法可以确保,例如,第二个线程
会看到对不可变
对象的引用。程序
从最终字段获得的保证应该仔细调整,并仔细了解代码中如何管理并发性。

Now, having said all of this, if, after a thread constructs an immutable object (that is, an object that only contains final fields), you want to ensure that it is seen correctly by all of the other thread, you still typically need to use synchronization. There is no other way to ensure, for example, that the reference to the immutable object will be seen by the second thread. The guarantees the program gets from final fields should be carefully tempered with a deep and careful understanding of how concurrency is managed in your code.

因此,如果我们甚至不能保证看到对f的引用,那么我们必须使用典型的同步机制(volatile,lock等),这些机制确实已经导致数据争用走开,对决赛的需求是我甚至不会考虑的。我的意思是,如果为了使f对其他线程可见,我们仍然需要使用volatile或synchronized块,并且它们已经使内部字段对其他线程可见......有什么意义(在线程安全术语中)首先让一个领域成为决赛?

So if we are not even guaranteed to see the reference to "f", and we must therefore use typical synchronization mechanisms (volatile, locks, etc.), and these mechanisms do already cause data races to go away, the need for final is something I would not even consider. I mean, if in order to make "f" visible to other threads we still need to use volatile or synchronized blocks, and they already make internal fields be visible to the other threads... what's the point (in thread safety terms) in making a field final in the first place?

推荐答案

我认为你误解了JLS示例的目的:

I think that you are misunderstanding what the JLS example is intended to show:

static void reader() {
    if (f != null) {
        int i = f.x;  // guaranteed to see 3  
        int j = f.y;  // could see 0
    } 
}

此代码不保证调用 reader()的线程将看到 f 的最新值。但它的含义是,如果您确实将 f 视为非空,则 fx 保证为 3 ...尽管我们实际上并没有进行任何明确的同步。

This code does not guarantee that the latest value of f will be seen by the thread that calls reader(). But what it is saying is that if you do see f as non-null, then f.x is guaranteed to be 3 ... despite the fact that we didn't actually do any explicit synchronizing.

这是隐式同步对于构造函数中的决赛有用吗?当然是...... IMO。这意味着每次访问不可变对象的状态时,我们都不会需要进行任何额外的同步。这是一件好事,因为同步通常需要缓存读取或直写,这会减慢你的程序。

Well is this implicit synchronization for finals in constructors useful? Certainly it is ... IMO. It means that we don't need to do any extra synchronization each time we accessed an immutable object's state. That is a good thing, because synchronization typically entails cache read-through or write-through, and that slows your program down.

但Pugh所说的是你会通常需要首先同步到获取对不可变对象的引用。他指出,使用不可变对象(使用 final 实现)并不能免除同步的需要......或者需要理解并发/同步实现你的申请。

But what Pugh is saying is that you will typically need to synchronize to get hold of the reference to the immutable object in the first place. He is making the point that using immutable objects (implemented using final) does not excuse you from the need to synchronize ... or from the need to understand the concurrency / synchronization implementation of your application.


问题在于我们仍然需要确保读者能够一个非空的f,这是唯一可能的,如果我们使用其他同步机制,它已经提供了允许我们看到3 for fx的语义如果是这种情况,为什么还要使用final作为线程安全的东西?

The problem is that we still need to be sure that reader will se a non-null "f", and that's only possible if we use other synchronization mechanism that will already provide the semantics of allowing us to see 3 for f.x. And if that's the case, why bother using final for thread safety stuff?

同步获取引用和同步之间存在差异使用参考。第一个我可能只需做一次。第二个我可能需要做很多次...同样的参考。即使它是一对一的,我仍然减少了同步操作的数量...如果我(假设)将不可变对象实现为线程安全的。

There is a difference between synchronizing to get the reference and synchronizing to use the reference. The first one I may need to do only once. The second one I may need to do lots of times ... with the same reference. And even if it is one-to-one, I have still halved the number of synchronizing operations ... if I (hypothetically) implement the immutable object as thread-safe.

这篇关于最终字段对于线程安全是否真的有用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆