线程中的最终字段语义

Isa*_*aac 6 java multithreading final thread-safety jls

这是来自JLS 17.5:

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

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我尝试重用这段代码来复制上面的情况,这就是我所拥有的:

public class FinalFieldThread extends Thread {

    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        ThreadB threadB = new ThreadB();

        threadB.start();
        threadA.start();
        //threadB.start();

    }
}

class ThreadA extends Thread {

    @Override
    public void run() {
        System.out.println("ThreadA");
        FinalFieldExample.writer();
    }
}

class ThreadB extends Thread {

    @Override
    public void run() {
        System.out.println("ThreadB");
        FinalFieldExample.reader();
    }
}
Run Code Online (Sandbox Code Playgroud)

我可以测试final是如何正确读取的,但是当它没有被正确读取时我怎么能复制(即在构造函数完成之前有一个对胎面的引用?)

Fav*_*ius 6

你在找什么

您要测试的内容称为"在施工期间不显示"此"参考"或" 可见性危险".请按照提供的顺序阅读以下链接.

  1. Java理论与实践:安全的施工技术
  2. JSR 133(Java内存模型)常见问题解答
  3. "最新"保证Java的最终字段的值是否延伸到间接引用?

示例代码

class FinalField
{
    final int x;
    int y;

    public FinalField()
    {
        Thread t = new Thread(new TestThread(this));
        t.start();

        y = 4;
        x = 3;
    }
}

class TestThread implements Runnable
{
    FinalField f;
    TestThread(FinalField f)
    {
        if(f.x != 3)
            System.out.println("value of x = " + f.x);

        this.f = f;
    }

    public void run() 
    {
        if(f.x != 3)
            System.out.println("value of x = " + f.x);
    }
}

public class Test
{
    public static void main(String[] args) 
    {
        for(int i=0; i<100; i++)
        {
            new FinalField();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Output

value of x = 0
value of x = 0
value of x = 0
.
.
.
value of x = 0
value of x = 0
value of x = 0
Run Code Online (Sandbox Code Playgroud)

输出说明

当我访问我的线程的构造函数中的最后一个字段时,那时该final字段x没有正确初始化,这就是我们得到的原因0.Whereas当我在run()那时访问相同的字段时,该final字段x被初始化为3.这是因为对escaping对象的引用FinalField.阅读我分享的第一个链接,它更加详细.