6

这是来自 JLS 17.5:

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

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

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

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();
    }
}

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

4

1 回答 1

7

你在找什么

您要测试的内容称为Don't publish the "this" reference during constructionVisibility Hazard。请按照提供的顺序阅读以下链接。

阅读

  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();
        }
    }
}

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

输出说明

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

于 2012-02-29T10:03:42.600 回答