简体   繁体   English

Java内存模型中的final字段

[英]final Fields In The Java Memory Model

Could you explain how the value of fy could be seen 0 instead of 4? 您能解释一下如何将fy的值看成0而不是4吗? Would that be because other thread writes updates the value to 0 from 4? 那是因为其他线程写入将值从4更新为0? This example is taken from jls https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5 此示例取自jls https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5

 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
            } 
        } 
    }

Assuming that we have two threads started, like this: 假设我们启动了两个线程,如下所示:

new Thread(FinalFieldExample::writer).start(); // Thread #1
new Thread(FinalFieldExample::reader).start(); // Thread #2

We might observe our program's actual order of operations to be the following: 我们可能会观察到程序的实际操作顺序如下:

  1. Thread #1 writes x = 3 . Thread #1写入x = 3
  2. Thread #1 writes f = ... . Thread #1写入f = ...
  3. Thread #2 reads f and finds that it is not null . Thread #2读取f并发现它不为null
  4. Thread #2 reads fx and sees 3 . Thread #2读取fx并看到3
  5. Thread #2 reads fy and sees 0 , because y does not appear to be written yet. Thread #2读取fy并看到0 ,因为y似乎尚未写入。
  6. Thread #1 writes y = 4 . Thread #1写入y = 4

In other words, Threads #1 and #2 are able to have their operations interleave in a way such that Thread #2 reads fy before Thread #1 writes it. 换句话说, Threads #1#2能够以某种方式进行操作交织,使得Thread #2Thread #1写入之前读取fy

Note also that the write to the static field f was allowed to be reordered so that it appears to happen before the write to fy . 还要注意,允许对static字段f的写操作进行重新排序,以便它似乎在写到fy之前发生。 This is just another consequence of the absence of any kind of synchronization. 这只是缺少任何类型的同步的另一个结果。 If we declared f as also volatile , this reordering would be prevented. 如果我们将f声明为volatile ,则将避免这种重新排序。


There's some talk in the comments about writing to final fields with reflection, which is true. 评论中有一些谈论写带有反射的final字段,这是事实。 This is discussed in §17.5.3 : §17.5.3对此进行了讨论:

In some cases, such as deserialization, the system will need to change the final fields of an object after construction. 在某些情况下,例如反序列化,系统将需要在构造后更改对象的final字段。 final fields can be changed via reflection and other implementation-dependent means. 可以通过反射和其他依赖实现的方式来更改final字段。

It's therefore possible in the general case for Thread #2 to see any value when it reads fx . 因此,在一般情况下, Thread #2在读取fx时可能会看到任何值。

There's also a more conventional way to see the default value of a final field, by simply leaking this before the assignment: 还有看到的默认值更传统的方式final场,通过简单地泄露this转让前:

class Example {
    final int x;

    Example() {
        leak(this);
        x = 5;
    }

    static void leak(Example e) { System.out.println(e.x); }

    public static void main(String[] args) { new Example(); }
}

I think that if FinalFieldExample 's constructor was like this: 我认为,如果FinalFieldExample的构造函数是这样的:

static FinalFieldExample f;

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

Thread #2 would be able to read fx as 0 as well. Thread #2也将fx读为0

This is from §17.5 : 这是来自§17.5

An object is considered to be completely initialized when its constructor finishes. 对象的构造函数完成后,就认为该对象已完全初始化 A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields. 保证只有在对象完全初始化之后才能看到对对象的引用的线程才能确保看到该对象的final字段的正确初始化值。

The more technical sections of specification for final contain wording like that as well. final规范的更多技术部分也包含类似的措辞。

Could you explain how the value of fy could be seen 0 instead of 4? 您能解释一下如何将fy的值看成0而不是4吗?

In Java, one of the important optimizations performed by the compiler/JVM is the reordering of instructions. 在Java中,编译器/ JVM执行的重要优化之一是指令的重新排序。 As long as it doesn't violate the language specifications, the compiler is free to reorder all instructions for efficiency reasons. 只要不违反语言规范,出于效率考虑,编译器可以自由地对所有指令重新排序。 During object construction, it is possible for an object to be instantiated, the constructor to finish, and its reference published before all of the fields in the object have been properly initialized. 在对象构造期间,可能会实例化对象,构造函数完成并在正确初始化对象中的所有字段之前发布其引用。

However, Java language says that if a field is marked as final then it must be properly initialized by the time the constructor finishes. 但是,Java语言表示,如果将字段标记为final则必须在构造函数完成时正确初始化它。 To quote from the section of the Java language specs you reference . 引用您引用Java语言规范部分。 Emphasis is mine. 重点是我的。

An object is considered to be completely initialized when its constructor finishes. 对象的构造函数完成后,就认为该对象已完全初始化。 A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields . 保证只有在对象完全初始化之后才能看到对对象的引用的线程才能保证看到该对象的final字段正确初始化值

So by the time the FinalFieldExample is constructed and assigned to f , the x field must be properly initialized to 3 however the y field may or may not have been properly initialized. 因此,在构造FinalFieldExample并将其分配给f必须x字段正确初始化为3,但是y字段可能已正确初始化,也可能未正确初始化。 So if thread1 makes the call to writer() and then thread2 makes the call to reader() and sees f as not null, y could be 0 (not yet initialized) or 4 (initialized). 因此,如果线程1调用writer() ,然后线程2调用reader()并且将f视为不为null,则y可以为0(尚未初始化)或4(已初始化)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM