这似乎是一个非常基本的问题,但我找不到明确的确认。
假设我有一个正确同步的类:
public class SyncClass {
private int field;
public synchronized void doSomething() {
field = field * 2;
}
public synchronized void doSomethingElse() {
field = field * 3;
}
}
如果我需要对那个类的实例有一个引用,并在线程之间共享,我仍然需要声明那个实例是 volatile 或 final,我说得对吗?如:
public class MainClass { // previously OuterClass
public static void main(String [ ] args) {
final SyncClass mySharedObject = new SyncClass();
new Thread(new Runnable() {
public void run() {
mySharedObject.doSomething();
}
}).start();
new Thread(new Runnable() {
public void run() {
mySharedObject.doSomethingElse();
}
}).start();
}
}
或者,如果 mySharedObject
不能是最终的,因为它的实例化取决于一些其他条件(与 GUI 的交互、来自套接字的信息等),事先不知道:
public class MainClass { // previously OuterClass
public static void main(String [ ] args) {
volatile SyncClass mySharedObject;
Thread initThread = new Thread(new Runnable() {
public void run() {
// just to represent that there are cases in which
// mySharedObject cannot be final
// [...]
// interaction with GUI, info from socket, etc.
// on which instantation of mySharedObject depends
if(whateverInfo)
mySharedObject = new SyncClass();
else
mySharedObject = new SyncClass() {
public void someOtherThing() {
// ...
}
}
}
});
initThread.start();
// This guarantees mySharedObject has been instantied in the
// past, but that still happened in ANOTHER thread
initThread.join();
new Thread(new Runnable() {
public void run() {
mySharedObject.doSomething();
}
}).start();
new Thread(new Runnable() {
public void run() {
mySharedObject.doSomethingElse();
}
}).start();
}
}
Final 或 volatile 是强制性的,MyClass
同步对其自身成员的访问这一事实不会免除注意确保引用在线程之间共享.是吗?
与的区别Difference between volatile and synchronized in Java
1- 提到的问题是关于 synchronized 和 volatile 作为替代方案,对于相同的字段/变量,我的问题是关于如何正确使用已经正确同步的类(即选择了 synchronized),考虑需要考虑的影响由调用者,可能在已同步类的引用上使用 volatile/final。
2- 换句话说,提到的问题/答案是关于锁定/易变的同一个对象,我的问题是:我如何确定不同的线程实际上看到同一个对象?在锁定/访问它之前。
当引用问题的第一个答案明确引用一个 volatile 引用时,它是关于一个不可变对象没有同步。第二个答案仅限于原始类型。 我确实发现它们很有用(见下文),但还不够完整,不足以消除对我在这里给出的案例的任何疑虑。
3- 所提到的答案是对一个非常开放的问题的非常抽象和学术的解释,根本没有代码;正如我在介绍中所说,我需要明确确认引用特定但很常见的问题的实际代码。当然,它们是相关的,但就像教科书与特定问题相关一样。 (我实际上在打开这个问题之前阅读了它,发现它很有用,但我仍然需要讨论一个特定的应用程序。)如果教科书解决了人们可能在应用它们时遇到的所有问题/疑虑,我们可能根本不需要 stackoverflow。
考虑到,在多线程中,您不能“只是尝试一下”,您需要正确理解并确定细节,因为竞争条件可以正确一千次,然后再出现一千 + 1 次可怕的错误。
最佳答案
是的,你是对的。有必要使对变量的访问也是线程安全的。您可以通过将其设为 final
或 volatile
来实现,或者确保所有线程在同步块(synchronized block)内再次访问该变量。例如,如果您不这样做,可能是一个线程已经“看到”变量的新值,但另一个线程可能仍然“看到”null
。
因此关于您的示例,当线程访问 mySharedObject
变量时,您有时会得到一个 NullPointerException
。但这可能只发生在具有多个缓存的多核机器上。
Java 内存模型
这里的重点是 Java 内存模型。它指出,如果更新发生在所谓的先发生关系中读取该状态之前,则线程只能保证看到另一个线程的内存更新。可以通过使用 final
、volatile
或 synchronized
来强制执行 happens-before 关系。如果您不使用这些构造中的任何一个,则一个线程的变量赋值永远不会保证对任何其他线程可见。
你可以认为线程在概念上有本地缓存,只要你不强制同步多个线程的缓存,一个线程就只是读取和写入它的本地缓存。这可能会导致两个线程在读取同一字段时看到完全不同的值。
请注意,还有一些额外的方法可以强制内存更改的可见性,例如,使用静态初始化程序。此外,新创建的线程总是看到其父线程的当前内存,而无需进一步同步。所以你的例子甚至可以在没有任何同步的情况下工作,因为你的线程的创建以某种方式强制发生在字段初始化之后。 但是依赖这样一个微妙的事实是非常危险的,如果您稍后在没有考虑这些细节的情况下重构您的代码,很容易崩溃。 Java Language Specification 中描述了(但难以理解)有关发生前关系的更多详细信息.
关于Java:引用同步对象是否需要 volatile/final?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29129820/