鉴于代码:
new Thread(new BackgroundWorker()).start();
直观地说,在线程退出之前,感觉BackgroundWorker实例应该是安全的,但是这种情况是这样的吗?为什么?
编辑:
所有这些热量基本上是由我在同一篇文章中询问至少两个不同的问题而产生的.标题中的问题有一个答案,代码示例导致不同的方向 - 两个可能的结果取决于内联.
发布的答案非常好.我将授予Software Monkey绿色复选框.请注意,Darron的答案同样有效,但Software Monkey解释了我遇到的问题; 这个答案对我有用.
谢谢大家让这件事变得难忘;)
是的,因为GC只能收集任何线程无法访问的对象,并且Thread必须持有对它的runnable的引用(或者它无法调用它).因此,显然,在线程运行时可以访问Runnable对象.
无论执行所需的语义如何,在此新线程或任何其他线程无法再访问之前,您的对象将不会进行GC.这将至少足以调用Runnable的run(),并且如果该线程能够到达Runnable实例,则在线程的整个生命周期内,因此JVM规范保证您的构造是安全的.
编辑:因为Darron打败了这个,有些人似乎对他的论点深信不疑,我将根据他的说法扩展我的解释.
假设除了Thread本身之外的任何人都不合法调用Thread.run(),
在这种情况下,Thread.run()的默认实现看起来是合法的:
void run() { Runnable tmp = this.myRunnable; // Assume JIT make this a register variable. this.myRunnable = null; // Release for GC. if(tmp != null) { tmp.run(); // If the code inside tmp.run() overwrites the register, GC can occur. } }
我认为在这种情况下,tmp仍然是对Thread.run()中执行的线程可以访问的runnable的引用,因此不符合GC的条件.
如果(由于一些莫名其妙的原因)代码看起来像:
void run() { Runnable tmp = this.myRunnable; // Assume JIT make this a register variable. this.myRunnable = null; // Release for GC. if(tmp != null) { tmp.run(); // If the code inside tmp.run() overwrites the register, GC can occur. System.out.println("Executed runnable: "+tmp.hashCode()); } }
显然,当tmp.run()执行时,tmp引用的实例不能是GC'd.
我认为Darron错误地认为,可达只意味着那些可以通过追逐以所有Thread实例作为根的实例引用而找到的引用,而不是被定义为任何执行线程都可以看到的引用.不管怎样,或者我错误地认为相反.
此外,达隆可以假定JIT编译器做他喜欢的任何改变-编译器不会允许改变执行代码的引用语义.如果我编写具有可访问引用的代码,则编译器无法优化该引用,并导致在该引用位于范围内时收集我的对象.
我不知道实际找到的可达物体的细节; 我只是推断我认为必须坚持的逻辑.如果我的推理不正确,那么在方法中实例化并且仅分配给该方法中的局部变量的任何对象将立即符合GC的条件 - 显然这不是也不可能.
此外,整个辩论都没有实际意义.如果只到达引用是Thread.run()方法,因为可运行的运行不会引用它的实例,并没有其他的参考实例存在,包括隐含此传递给run()方法(在字节码,不作为申报参数),那么也没关系对象实例是否收集-这样做,顾名思义,可以造成任何损害,因为要执行,如果隐含的代码它并不需要此已优化掉.既然如此,即使Darron是正确的,最终的实际结果是由OP假定的结构是完全安全的.无论哪种方式.没关系.让我再重复一次,只是为了清楚 - 最后分析没关系.
是的,这是安全的.原因并不像你想象的那么明显.
仅仅因为BackgroundWorker中的代码正在运行并不能使其安全 - 有问题的代码实际上可能不会引用当前实例的任何成员,从而允许优化"this".
但是,如果仔细阅读java.lang.Thread类的run()方法的规范,您将看到Thread对象必须保留对Runnable的引用才能完成其合同.
编辑:因为我已经多次对这个答案进行了投票,我将扩展我的解释.
假设除了Thread本身之外的任何人都不合法调用Thread.run(),
在这种情况下,Thread.run()的默认实现看起来是合法的:
void run() { Runnable tmp = this.myRunnable; // Assume JIT make this a register variable. this.myRunnable = null; // Release for GC. if (tmp != null) tmp.run(); // If the code inside tmp.run() overwrites the register, GC can occur. }
我一直在说的是,JLS中的任何内容都不会因为线程正在执行实例方法而阻止对象被垃圾收集.这是使得最终确定变得如此困难的部分原因.
对于那些比我更了解它的人来说非常难以理解的细节,请参阅并发兴趣列表中的讨论主题.
这很安全. JVM保留对每个线程的引用.Thread保持传递给其构造函数的Runnable实例.因此Runnable可以很容易地访问,并且不会在Thread的生命周期内收集.
我们知道,由于Thread.run()的javadoc,Thread保存了对runnable的引用:
如果使用单独的Runnable运行对象构造此线程,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回.