Simple thread crashes Android app

Viewed 1685

I found a very peculiar issue when creating an launching a thread inside an Android app.

If I have the following class thread:

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
}

and add it somewhere in the Activity's onCreate(...) method like:

public class MyActivity extends Activity {
    TroubleThread  myThread;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        //....
        myThread = new TroubleThread();
        myThread.setRunning(true);
        myThread.start();
    }
}

the app will crash.

But if I change the run() method to:

@Override
    public void run() {
        while (running){ //NOTE THE USE OF DIRECT FIELD ACCESS INSTEAD OF METHOD
        }//end while
    }//end run

it stops from crashing.

Even if I solved my problem by using locks, notify() and wait() the question still remains:

Why when using direct access to the field the app continues to work while when using the method it crashes?

1 Answers

First of all, since you did not provide a MCVE, other people cannot reproduce your problem. That is unfortunate because it means we cannot be sure what the real cause of the problem is. We can only propose hypotheses.

Some people have hypothesized that your problem is due to something else; e.g. an infinite loop on the GUI thread. That is plausible, but there is no clear evidence for this. (And we can't see the code ....)

My hypothesis is that this is a "memory visibility" issue. The Java Language Specification has a chapter that defines the circumstances under which one thread is guaranteed to see a value written to memory by another thread. The rules are rather complicated and technical, but the essence is that you need to analyzing whether there is a happens before relationship between a memory write by one thread and a subsequent memory read by another thread. A number of things will give you that relationship:

  • the threads write / reading a shared volatile variable
  • the threads synchronizing using the same lock
  • a thread start
  • a thread join
  • completion of a constructor (for final variables)

However, none of these things exist in your program with respect to the running variable. This means that there is no guarantee that the thread that is looping on the running variable will see the results of the setRunning call made by the other thread:

  • It might see the changes immediately
  • It might see the changes later
  • It might never see them

All three behaviors are possible .... in the absence of a happens before relationship.

So why does one version of the code behave differently to the other?

We can't be sure. To actually be sure, someone would need to do a deep analysis of the native (machine) code for your example. It could be something to do with (legal) reordering that the optimizer has done in one case and not the other. It could be a subtle timing effect.

But either way, the JLS says the the compiler is not obliged to make sure that the write is visible. Why? Because this code breaks the rules of the memory model.

Solutions:

In this case, the simplest solution is to declare running as volatile.

Another solution is to declare isRunning() and setRunning as synchronized methods.

Either of these are sufficient to provide the happens before relationship ... and guarantee that isRunning() sees the update made by setRunning().

Related