5

假设我有一个从另一个线程更新的 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 的线程读取用户字段时,它是否看到用户处于新鲜状态?如何以适当的线程安全方式进行操作?

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

4

6 回答 6

4

这个代码线程安全吗?...它看到用户处于新鲜状态吗?

不是特别 -user在你的代码中是 final 的事实对线程安全几乎没有影响,除了它不能被替换。

应该更改的位是由 设置的实例变量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该类,则必须执行创建内存屏障的同步。在其最简单的形式中,您可以使用访问来包围您对usersynchronized访问。

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

补充: - 事实证明(见这里)这实际上可以这样完成:

volatile int barrier = 0;
...
user.setSomething(something);
// Forces **all** cached variable to be flushed.
barrier += 1;
于 2013-05-23T08:37:52.783 回答
4

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

于 2013-05-23T08:40:55.733 回答
0

final 只会使引用不可重新分配,但如果引用指向可变类,您仍然可以更改该对象内部的状态,这会导致广告不安全。

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

如果不是这样,那么您必须正确同步其方法以使其线程安全。

于 2013-05-23T09:08:38.437 回答
0

关于线程,final字段只是保证在构造函数逃逸的情况下是一致的,因为JSR-133关于内存屏障机制:

对象的最终字段的值在其构造函数中设置。假设对象是“正确”构造的,一旦构造了对象,分配给构造函数中最终字段的值将对所有其他线程可见,而无需同步。此外,那些最终字段引用的任何其他对象或数组的可见值将至少与最终字段一样是最新的。正确构造对象意味着什么?它只是意味着在构造过程中不允许对正在构造的对象的引用“逃逸”。(有关示例,请参见安全施工技术。) 换句话说,不要将对正在构造的对象的引用放在其他线程可能看到的任何地方;不要将其分配给静态字段,不要将其注册为任何其他对象的侦听器,等等。这些任务应该在构造函数完成后完成,而不是在构造函数中。

但是,没有任何东西可以确保剩余对象生命周期中任何最终字段的自动线程安全(意味着在包装类的构造函数执行之后)。. 确实,Java 中的不变性是一种用词不当:

现在,用普通话来说,不变性意味着“不改变”。在 Java 中,不变性并不意味着“不变”。这意味着“可以从 final 字段传递到,自设置 final 字段以来没有更改,并且对包含 final 字段的对象的引用没有逃脱构造函数”。

于 2013-05-23T09:55:45.493 回答
0

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

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

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

这假设用户对象仅在您的线程中被异步访问。还要保持同步块简短。

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

祝你好运!

于 2013-05-23T09:52:19.757 回答
0

是的,这是安全的。看

Java 语言规范 (Java 8) 第 17.4.4 章

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

T2 可以通过调用 T1.isAlive() 或 T1.join() 来完成此操作。

将其与17.4.5 放在一起。订单前发生

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

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

于 2016-08-11T15:10:10.623 回答