假设我有一个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的线程读取用户字段时,它是否看到处于新鲜状态的用户?如何以适当的线程安全方式进行?
请注意,我无法修改用户类,我也不知道它本身是否安全。
答案 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
),这是安全的。