简单的线程导致Android应用崩溃

5
我是一个有用的助手,可以翻译文本。
我发现在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()解决了我的问题,问题仍然存在:

为什么在直接访问字段时应用程序继续工作,而在使用方法时会崩溃?


2
你确定你使用的是 boolean running; 而不是 Boolean running 吗?你能发一下堆栈跟踪吗? - Blackbelt
你尝试过初始化变量的值,以便getRunning()肯定会返回某些内容而不是未初始化的变量吗?另外,这里正在问什么问题,你似乎已经修复了崩溃...? - Shark
1
你的错误是什么? - GVillani82
1
没有任何堆栈跟踪? - GVillani82
2
我认为问题不在你的 TroubleThread 上,而是 UI 线程,可能在等待某些需要很长时间的东西。我已经测试了你的解决方案,直接访问布尔值和通过 get 方法都可以正常工作。所以,我认为这应该取决于你在 UI 线程中要做什么。但是,你可能只需要同步对共享变量的访问。 - GVillani82
显示剩余6条评论
1个回答

0
首先,由于您没有提供MCVE,其他人无法重现您的问题。这很不幸,因为这意味着我们无法确定问题的真正原因。我们只能提出假设。
有些人假设您的问题是由其他原因引起的;例如,在GUI线程上有一个无限循环。这是有道理的,但没有明确的证据支持这一点。(而且我们看不到代码....)
我的假设是这是一个“内存可见性”问题。Java语言规范有一章定义了一个线程在何种情况下可以保证看到另一个线程写入内存的值。规则相当复杂和技术性,但本质上是需要分析一个线程是否存在“happens before”关系,即一个线程写入内存,另一个线程随后读取内存。许多事情会给你这个关系:
  • 线程写入/读取共享的易失性变量
  • 线程使用相同的锁进行同步
  • 线程启动
  • 线程加入
  • 构造函数完成(对于final变量)

然而,对于running变量,您的程序中没有这些东西存在。这意味着循环running变量的线程无法保证看到其他线程调用setRunning函数所产生的结果:

  • 可能会立即看到更改
  • 可能稍后看到更改
  • 可能永远不会看到更改

在缺乏happens before关系的情况下,三种行为都是可能的。

那么为什么代码的一个版本和另一个版本的行为不同呢?

我们不能确定。要实际确定,需要有人对您的示例的本地(机器)代码进行深入分析。它可能与优化器在一种情况下执行了(合法)重排序有关,而在另一种情况下没有。它可能是微妙的时间效应。

但无论如何,JLS说编译器没有义务确保写入可见性。为什么?因为这段代码违反了内存模型的规则。

解决方案:

在这种情况下,最简单的解决方案是将running声明为volatile
另一个解决方案是将isRunning()setRunning声明为synchronized方法。
这两种方法都足以提供 happens before 关系,并保证isRunning()看到setRunning()所做的更新。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接