3

基本上以下工作,但由于我读到了 final 关键字,我不确定如果不同的线程访问它,我是否必须声明 name final?

提前致谢。

public class Test4 {

    // to ensure thread-safety do we have to declare the variable name final ?
    private String name;

    public Test4 (String name) {
        this.name = name;
    }

    public void start() {
        new MyThread().start();
    }

    private class MyThread extends Thread {

        public void run() {
            System.out.println(name);
        }
    }

    public static void main(String[] args) {
        Test4 t = new Test4("Don't know if I am threadsafe");
        t.start();
    }

}
4

6 回答 6

4

final修饰符 - 同时防止成员被重新分配 -影响给定代码1的正确性

从Java 5 语言规范的17.4.4 同步顺序部分:

同步顺序是执行的所有同步操作的总顺序。同步操作会导致操作上的同步关系,定义如下:

  • ..
  • 启动线程的动作与它启动的线程中的第一个动作同步
  • ..

然后,由于设置name成员的线程是启动线程的线程,所以同步顺序是有保证的。(Synchronizes-with 意味着Happens-before ordering。)

注意:

  • 成员name只需要在启动线程之前设置:也就是说,不需要在构造函数中设置它来保证同步。
  • 不能保证同步顺序 - 因此它不能保证已经运行的线程或在其他地方创建的线程之间的发生之前或值可见性!

然而,final字段确实给人更舒服的感觉(参考17.5 Final Field Semantics):

当一个对象的构造函数完成时,它被认为是完全初始化的。只能在对象完全初始化后才能看到对该对象的引用的线程可以保证看到该对象的最终字段的正确初始化值

在这种情况下,使用 final 字段,可以保证在构造函数完成后,该值在每个线程上都是可见的。“构造函数泄漏”可能会违反此保证。)


1在提供的代码中,“非最终”name成员仅在线程启动之前分配一次。

不同的、不那么琐碎的程序中,可能会暴露出其他同步问题。此答案检查删除是否final会改变所提供代码的正确性。

话虽如此,我认为同时使用不可变变量(final)和不可变对象是“好习惯”——尤其是在处理线程时。无需了解 JVM 的小奥秘细节,而是做安全的、经过验证的事情,并争取明显的正确性,而不是聪明或“性能”。

也可以看看:

于 2013-01-16T09:37:58.247 回答
3

final 变量是immutable,一旦构造它就不能更改值,因此它不存在并发问题。

于 2013-01-16T09:09:43.587 回答
2

如果没有 final,您将无法获得正确的字段值。

更改字段的值后,线程可能会获得旧值。

检查JMM的可见性。

volatile 的另一个环节

发生在规则之前

发生在 JMM 之前

于 2013-01-16T09:11:06.170 回答
1

你能找一个AtomicReference或也许volatile吗?这取决于您所说的线程安全是什么意思?

// Atomic to allow deeper control of updates.
private AtomicReference<String> name = new AtomicReference<String>();
// Volatile to ensure it is not cached.
private volatile String vName;

public Test(String name) {
  this.name.set(name);
  this.vName = name;
}

public void start() {
  new MyThread().start();
}

private class MyThread extends Thread {
  public void run() {
    System.out.println(name.get());
    System.out.println(vName);
  }
}
于 2013-01-16T09:23:17.760 回答
0

final多线程没有什么,但final如果你的字段不应该改变并且在类的构造函数中初始化,你应该放。这意味着 fild 不能在后面更改。

于 2013-01-16T09:11:16.820 回答
0

由于 String 是不可变的,并且您声明字段 final ,并且所有线程在字段分配后访问它,因此肯定不会有并发问题,因为该字段仅用于读取操作,与使用 StringBuilder 的情况相反。

于 2013-01-17T10:50:32.740 回答