简单线程使 Android 应用程序崩溃

Posted

技术标签:

【中文标题】简单线程使 Android 应用程序崩溃【英文标题】:Simple thread crashes Android app 【发布时间】:2016-07-09 02:29:31 【问题描述】:

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() 解决了我的问题,问题仍然存在:

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

【问题讨论】:

您是使用boolean running; 而不是Boolean running 吗?你能发布堆栈跟踪吗? 您是否尝试过初始化该值以使getRunning() 肯定返回某些内容而不是未初始化的变量?另外,这里问的问题是什么,您似乎已经修复了崩溃...? 你的错误是什么? 没有任何堆栈跟踪? 我认为问题不是你的TroubleThread,而是UI线程,它可能正在等待需要大量时间的东西。我已经通过直接访问布尔值和通过 get 方法测试了您的解决方案,它工作正常。因此,我认为这应该取决于您在 UI 线程中尝试执行的操作。但是,您可能只需要同步对共享变量的访问即可。 【参考方案1】:

首先,由于您没有提供 MCVE,其他人无法重现您的问题。这是不幸的,因为这意味着我们无法确定问题的真正原因是什么。我们只能提出假设。

有些人假设您的问题是由其他原因引起的;例如GUI线程上的无限循环。这是有道理的,但没有明确的证据证明这一点。 (而且我们看不到代码......)

我的假设是这是一个“内存可见性”问题。 Java 语言规范有一章定义了一个线程保证看到另一个线程写入内存的值的情况。规则是比较复杂和技术性的,但本质是需要分析一个线程写入的内存和另一个线程读取的后续内存之间是否存在happens before关系。有很多事情可以让你建立这种关系:

线程写入/读取共享 volatile 变量 线程同步使用相同的锁 线程开始 线程连接 构造函数的完成(对于final变量)

但是,对于running 变量,您的程序中不存在这些东西。这意味着无法保证running 变量上循环的线程将看到另一个线程进行的setRunning 调用的结果:

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

所有三种行为都是可能的......在没有发生之前关系的情况下。

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

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

但无论哪种方式,JLS 都表示编译器没有义务确保写入可见。为什么?因为这段代码打破了内存模型的规则

解决方案:

在这种情况下,最简单的解决方案是将running 声明为volatile

另一种解决方案是将isRunning()setRunning 声明为synchronized 方法。

其中任何一个都足以提供 happens before 关系...并保证isRunning() 看到setRunning() 所做的更新。

【讨论】:

以上是关于简单线程使 Android 应用程序崩溃的主要内容,如果未能解决你的问题,请参考以下文章

在线程中运行的 JsonReader 使应用程序崩溃

为啥后台线程中未处理的异常不会使应用程序域崩溃?

线程中的网络请求使我的应用程序崩溃

在 C++ 中创建 std 线程会使程序崩溃

使 Android 应用程序崩溃的最佳方法是啥?

如何以编程方式使 Android 应用程序崩溃?