是的,这是一个学术问题,我知道人们会抱怨我没有发布任何代码 但我真的对这个问题感到震惊,真的不知道从哪里开始。我真的很感激解释,也许还有一些代码示例。
如果对象构造函数启动了执行该方法的新线程 运行一个匿名的内部类对象,有可能是这个新的 线程可以在它之前访问它周围的外部对象 完全构建并完全初始化其字段。你好吗? 防止这种情况发生?
答案 0 :(得分:20)
这被称为“泄漏这个”。这里有代码
public class Test {
// this is guaranteed to be initialized after the constructor
private final int val;
public Test(int v) {
new Thread(new Runnable() {
@Override public void run() {
System.out.println("Val is " + val);
}
}).start();
this.val = v;
}
}
猜猜它会是什么(可能,因为它是一个线程)打印。我使用final
字段强调对象在完全初始化之前被访问(最终字段必须在每个构造函数的最后一行之后明确分配)
如何恢复
当你在构造函数中时,你不想传递this
。这也意味着您不希望在同一个类(非静态,非私有)中调用非最终虚拟方法,而不使用内部类(匿名类是内部类),这些类隐含地与封闭类相关联实例,因此他们可以访问this
。
答案 1 :(得分:10)
首先考虑单线程情况:
每当通过new
创建对象时,都会调用其构造函数(希望)在返回对此对象的引用之前初始化新对象的字段。也就是说,从调用者的角度来看,这个new
几乎就像一个原子操作:
在致电new
之前,没有任何对象。从new
返回后,对象存在完全初始化。
所以一切都很好。
当多个线程发挥作用时,情况会略有变化。但我们必须仔细阅读你的引用:
...已经完全构建并且其字段已完全初始化。
关键点是fully
。您的问题的主题行在创建"之前表示"但这里的含义不是在创建对象之前,而是在对象创建和初始化之间。在多线程情况下,由于这一点(时间从左向右流动),new
不再被视为(伪)原子:
Thread1 --> create object --> initialize object --> return from `new`
^
|
| (messing with the object)
Thread2 ------------------/
那么Thread2如何搞乱对象呢?它需要对该对象的引用,但由于new
只会在创建和初始化之后返回对象,所以这应该是不可能的,对吗?
嗯,不 - 有一种方法仍然可以 - 即如果在对象的构造函数中创建了线程2。然后情况就是这样:
Thread1 --> create object --> create Thread2 --> initialize object --> return from `new`
| ^
| |
| | (messing with the object)
\-----/
由于在创建了对象之后创建了(但在它完全初始化之前),因此已经有一个对Thread2可以获取的对象的引用。一种方法是简单地,如果Thread2的构造函数显式地将对象的引用作为参数。另一种方法是使用Thread2的run
方法的对象的非静态内部类。
答案 2 :(得分:2)
我不完全同意Pablos的回答,因为它在很大程度上取决于你的初始化方法。
public class ThreadQuestion {
public volatile int number = 0;
public static void main(String[] args) {
ThreadQuestion q = new ThreadQuestion();
}
public ThreadQuestion() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(number);
}
});
try {
Thread.sleep(500);
} catch(Exception e) {
e.printStackTrace();
}
number = 1;
t.start();
}
}
当你
t.start()
放在最后,打印正确的数据。t.start()
,它将打印0 t.start()
放置在可以打印1的分配之前(不可确定)在3.)上玩一个心灵游戏。你可以说一个小小的" 1个简单数据类型的赋值将按预期工作,但如果您创建数据库连接,则无法获得可靠的结果。
不要犹豫提出任何问题。
答案 3 :(得分:2)
我会改变问题的标题,因为线程不是自己访问,而是第二个访问第一个。我的意思是: 你有一个线程,创建一个对象。 在此对象的构造函数内,您声明了一个实现Runnable的匿名内部类。 在第一个线程的相同构造函数中,您启动一个新线程来运行您的匿名内部类。 因此,你有两个线程。如果你想确保新的线程在构造函数完全结束之前没有做任何事情,那么我会在构造函数中使用一些锁。这样,第二个线程可以启动,但会等到第一个线程结束。
public class A { int final number; A() { new Thread( new Runnable() { public void run() { System.out.pritnln("Number: " + number); } }).start(); number = 2; } }
答案 4 :(得分:1)
这样的情况呢?
public class MyClass {
private Object something;
public MyClass() {
new Thread() {
public void run() {
something = new Object();
}
}.start();
}
}
根据使用的实际代码,行为可能会有所不同。这就是为什么构造函数应该仔细制作,以便他们不必调用非私有方法(子类可以覆盖它,允许在超类完全初始化之前从子类访问超类this
。 )。虽然这个特定的例子涉及单个类和一个线程,但它与参考泄漏问题有关。