在Android应用程序中创建启动线程时,我发现了一个非常特殊的问题。
如果我有以下类线程:
public class TroubleThread extends Thread{
boolean running;
public boolean isRunning() {
return running;
}
public void setRunning(boolean running) {
this.running = running;
}
@Override
public void run() {
while (isRunning()){
}//end while
}//end run
}
并将其添加到Activity的onCreate(...)方法中的某处,如:
public class MyActivity extends Activity {
TroubleThread myThread;
@Override
public void onCreate(Bundle savedInstanceState) {
//....
myThread = new TroubleThread();
myThread.setRunning(true);
myThread.start();
}
}
应用程序将崩溃。
但是如果我将run()方法更改为:
@Override
public void run() {
while (running){ //NOTE THE USE OF DIRECT FIELD ACCESS INSTEAD OF METHOD
}//end while
}//end run
停止崩溃。
即使我通过使用锁,notify()和wait()解决了我的问题,问题仍然存在:
为什么当使用直接访问该字段时应用程序继续工作,而在使用该方法时它会崩溃?
答案 0 :(得分:0)
首先,由于您未提供MCVE,因此其他人无法重现您的问题。这是不幸的,因为这意味着我们无法确定问题的真正原因是什么。我们只能仅提出假设。
有人认为您的问题是由其他原因引起的;例如GUI线程上的无限循环。这是合理的,但没有明确的证据。 (而且我们看不到代码...。)
我的假设是这是一个“内存可见性”问题。 Java语言规范的一章定义了保证一个线程查看另一个线程写入内存的值的情况。这些规则相当复杂且技术性强,但是本质是您需要分析一个线程写入的内存与另一个线程读取的后续内存之间是否存在发生的关系。很多事情都会给你这种关系:
final
变量)但是,程序中与running
变量有关的所有这些都不存在。这意味着不能保证,在running
变量上循环的线程将看到另一个线程进行的setRunning
调用的结果:
这三种行为都是可能的....在没有发生之前关系的情况下。
那为什么一个版本的代码的行为与另一个版本不同?
我们不确定。为了真正确定,有人需要对您的示例进行本机(机器)代码的深入分析。优化器在一种情况下而不是另一种情况下可能与(合法)重新排序有关。这可能是微妙的计时效果。
但是无论哪种方式,JLS都说编译器没有义务确保写操作可见。为什么?因为这段代码违反了内存模型的规则。
解决方案:
在这种情况下,最简单的解决方案是将running
声明为volatile
。
另一种解决方案是将isRunning()
和setRunning
声明为synchronized
方法。
这两个条件中的任何一个都足以提供发生之前的关系...并保证isRunning()
看到setRunning()
所做的更新。