最终场的线程安全性

时间:2013-05-23 08:34:30

标签: java multithreading thread-safety

假设我有一个JavaBean 用户,它是从另一个线程更新的:

public class A {

    private final User user;

    public A(User user) {
        this.user = user;
    }

    public void aMethod() {
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                ...a long running task..
                user.setSomething(something);
            }

        });
        t.start();
        t.join();
    }

    public void anotherMethod() {
        GUIHandler.showOnGuiSomehow(user);
    }
}

此代码线程是否安全?我的意思是,当创建A实例并调用A.aMethod的线程读取用户字段时,它是否看到处于新鲜状态的用户?如何以适当的线程安全方式进行?

请注意,我无法修改用户类,我也不知道它本身是否安全。

6 个答案:

答案 0 :(得分:4)

  

此代码线程是否安全? ......它是否看到用户处于新鲜状态?

不是特别 - 代码中user是最终的这一事实对线程安全几乎没有任何影响,除了它无法替换之外。

应该更改的位是由setSomething设置的实例变量。它应标记为volatile

class User {
  // Marked `volatile` to ensure all writes are visible to other threads.
  volatile String something;

  public void setSomething(String something) {
    this.something = something;
  }

}

但是,如果(如您所示)您无法访问User类,则必须执行创建内存屏障的同步。最简单的形式是,您可以通过user访问权限访问synchronized

synchronized (user) {
  user.setSomething(something);
}

补充: - 事实证明(见here)这实际上可以这样做:

volatile int barrier = 0;
...
user.setSomething(something);
// Forces **all** cached variable to be flushed.
barrier += 1;

答案 1 :(得分:4)

将标记字段标记为final只表示无法更改引用。它对类User的线程安全性没有任何意义。如果访问字段的此类的方法是同步的(或使用其他同步技术),则它是线程安全的。否则就不是。

答案 2 :(得分:0)

final仅使引用不可重新赋值,但如果引用指向可变类,则仍可以更改该对象内的状态,这会导致不安全。

如果User类是不可变的,那么您的代码只是线程安全的。 User的所有属性都不能在对象外部进行更改,类中的所有引用都指向其他不可变类。

如果不是这种情况,则必须正确同步其方法以使其成为线程安全的。

答案 3 :(得分:0)

  

请注意,我无法修改用户类,我不知道它本身是否是线程安全的。

访问User对象时必须同步访问权限。 例如,您可以使用User对象进行同步,因此只需使用以下内容包装用户对象上的每个访问权限:

synchronized(user) {
  // access some method of the user object
}

假设只在您的线程中异步访问用户对象。同时保持同步块短。

您还可以围绕用户对象构建线程安全包装器。我建议如果你有很多不同的调用,代码会更清晰,更好地阅读。

祝你好运!

答案 4 :(得分:0)

关于线程,final字段只保证在constructor escape的情况下保持一致,因为JSR-133关于内存屏障机制:

  

对象的最终字段的值在其构造函数中设置。   假设对象是“正确”构造的,一旦对象是   构造,分配给最终字段的值   所有其他线程都可以看到构造函数   同步。此外,任何其他对象的可见值   或者那些最终字段引用的数组至少为   作为最终领域的最新。对象是什么意思   结构合理吗?它只是意味着没有对象的引用   正在建造中允许在施工期间“逃离”。 (看到   安全施工技术的例子。)换句话说,不要   在任何地方放置对正在构造的对象的引用   另一个线程可能会看到它;不要将它分配给静态   字段,不要将其注册为任何其他对象的侦听器,等等   上。这些任务应该在构造函数完成后完成,而不是在   构造函数。

然而,没有什么能确保剩余对象生命中任何最终字段的自动线程安全性(意味着在包装类的构造函数执行之后)。。实际上,Java中的不变性纯属误:

  

现在,按照通常的说法,不变性意味着“不会改变”。   不可变性并不意味着Java中的“不会改变”。这意味着“是   从最终字段可传递到达,自从以来没有改变   设置了final字段,并对包含该对象的对象进行了引用   最终字段没有逃脱构造函数“。

答案 5 :(得分:0)

是的,这是安全的。参见

Java Language Specification (Java 8) Chapter 17.4.4

  

线程T1中的最终操作与另一个检测到T1已终止的线程T2中的任何操作同步。

     

T2可以通过调用T1.isAlive()或T1.join()来实现。

将其与17.4.5. Happens-before Order

放在一起
  

可以通过先发生关系来排序两个动作。如果一个动作发生在另一个动作之前,那么第一个动作在第二个动作之前可见并且在第[..]如果动作x与后续动作y同步,那么我们也有hb(x,y)。

因此,在代码中调用t.join();后,您将看到更新后的更改。从"创建A实例并调用A.aMethod"的线程调用aMethod之后和调用t.join之前不可能读取值(因为它忙于方法aMethod),这是安全的。