我在 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() 解决了我的问题,问题仍然存在:
为什么在使用直接访问该字段时应用程序继续工作,而在使用它崩溃的方法时?
最佳答案
首先,由于你没有提供MCVE,其他人无法重现你的问题。这是不幸的,因为这意味着我们无法确定问题的真正原因是什么。我们只能提出假设。
有些人假设您的问题是由其他原因造成的;例如GUI 线程上的无限循环。这是有道理的,但没有明确的证据证明这一点。 (而且我们看不到代码....)
我的假设是这是一个“内存可见性”问题。 Java 语言规范有一章定义了一个线程在什么情况下保证看到另一个线程写入内存的值。规则比较复杂,技术性强,其实质就是要分析一个线程写内存和另一个线程读内存之间是否存在happens before关系。许多事情会给你这种关系:
- 线程写/读一个共享的volatile变量
- 线程使用同一个锁进行同步
- 线程开始
- 线程连接
- 完成构造函数(用于
final
变量)
但是,关于 running
变量,这些东西都不存在于您的程序中。这意味着无法保证在running
变量上循环的线程会看到另一个调用setRunning
的结果主题:
- 它可能会立即看到变化
- 稍后可能会看到更改
- 它可能永远看不到它们
所有这三种行为都是可能的......在没有先于关系的情况下。
那么为什么一个版本的代码与另一个版本的行为不同?
我们不能确定。实际上,有人需要对您的示例的 native (机器)代码进行深入分析。这可能与优化器在一种情况下而不是另一种情况下完成的(合法)重新排序有关。这可能是一个微妙的时间效应。
但无论哪种方式,JLS 都表示编译器没有义务确保写入可见。为什么?因为这段代码违反了内存模型的规则。
解决方案:
在这种情况下,最简单的解决方案是将running
声明为volatile
。
另一种解决方案是将isRunning()
和setRunning
声明为同步
方法。
其中任何一个都足以提供先于 关系...并保证isRunning()
看到setRunning()所做的更新
.
关于java - 简单线程使 Android 应用程序崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36150385/