Frequently Asked Android Concurrency Questions

Abstract

This is my list of question that should be frequently asked about concurrency in Android



Table of Contents

1.  The Basics
    1.1.  What is Concurrency, really?
    1.2.  Why do I need Concurrency, anyway?
    1.3.  What are 'threads', anyway?
    1.4.  What is so special about the 'UI thread'?
    1.5.  I see a lot of threads in the debugger. Which do I need to know about?
    1.6.  What support does the Android SDK offer for Concurrency?
    1.6.1  How do I choose between AsyncTask and Looper/Handler?
    1.7.  What are 'Concurrency Patterns' and how are they helpful?
    1.8.  What are 'Condition Variables'?
    1.9.  What are 'Light Weight Processes'?
    1.10.  Is there a difference between 'Light Weight Processes' and 'threads'?
    1.11.  What is the difference between a process and a thread?
    1.12.  What is an "intrinsic lock"?
    1.13.  What is a "private lock"?
    1.14.  What is special about Concurrency Programming in the Android Environment




1.   The Basics

This section covers the basics of concurrency under Android. It does not presume a lot of prior Java Concurrency knowledge, but the description of Java support for concurrency is cursory. The reader should refer to the Oracle tutorial on Concurrency for implementation details. But after reading this FAQ, the tutorial should make a lot more sense.



1.1   What is Concurrency?

Concurrency is a programming model that does not assume sequential execution of all commands. Moreover, it makes minimal assumptions about the timing of execution of instructions in separate threads; it is more general than parallel computing. This minimzed set of assumptions allows the one model to be useful both for multiprocessor environments and single processor.

Some authors make a distinction between physical concurrency and logical concurrency. The former is on multiprocessors, the latter on single core processors.



1.2   Why do I need Concurrency, anyway?

Concurrency is often useful for doing background calculations or improving application responsiveness. But it is particularly important in Android to avoid the dreaded ANR. Any computation that might hold up the UI thread, whether waiting for external event or just compute intensive, should be done in a background thread instead, often in a Service.



1.3   What are 'threads', anyway?

"Multithread Programming in Java" defined a 'thread' as follows:

Every thread is a distinct control flow which can execute its instructions independently which [in turn] permits one process with multiple threads to realize various tasks in a concurrent manner.

The big difference between a 'thread' and a 'process' is that threads share more state with each other. Processes do not share variables. Neither will share stacks.

Comparing with the description of Concurrency above, we see that the thread is the unit of guaranteed sequential execution in the concurrent programming model. That is, we are guaranteed instructions are executed in sequential order in any one given thread (relative to other instructions in the same thread), it is the timing of execution of instructions in other threads compared to that given thread we know nothing about (more precisely: we have no a priori knowledge).



1.4   What is so special about the 'UI thread'?

There are two things that are special about the UI thread. First of all, it is the default thread, which is why it is also called the main thread. But even more important for the Android developer to remember, it is the thread which absolutely must not be blocked. Otherwise the Android OS assumes something has gone terribly wrong, and displays the dreaded ANR, the "Application Not Responding" alert.

This must be avoided at all costs, since it is the ultimate in bad user experience. This is why one of the most common applications of multi-threading in Android is simply to avoid the ANR by moving any potentially blocking operations out of this thread.


1.5   I see a lot of threads in the debugger. Which do I need to know about?

The Android JVM, Dalvik, shares many features in common with any other JVM. In particular, it creates several threads, not just the main thread.

Exactly which threads get created even for "Hello, World!" differs from JVM to JVM, but in Android, we have the main thread a.k.a. UI thread, binder threads, threads for garbage collection, even a "signal catcher" thread.

Out of all of these, by far the most important is, of course, the UI thread: this is the only thread allowed to write to the screen or interact with the user. As mentioned in 1.4, this is also the thread you must not block or delay, another example of its importance.



1.6   What support does the Android SDK offer for Concurrency?

The Android Platform offers all the same Java Concurrency support the standard Java Platform offers: synchronized, wait() notify(), synchronized collection classes such as BlockingQueue. But long available and specific to the Android platform are the classes Message, MessageQueue, Looper and Handler. More recently, AsyncTask was added.



1.6.1   How do I choose between AsyncTask and Looper/Handler?

The basic rule of thumb is: for AsyncTask, you want tasks that are one-shots (i.e. run once to completion) and not too long-lived. By that I mean, yes, it can be long enough to avoid the dreaded ANR (5 seconds), but not for hours and hours. Downoading thumbnails or other moderately large files are an example. For the longer tasks, use a Service, and don't forget to create a your own "worker thread" in said Service, using Looper/Handler to manage communication with the foreground.



1.7   What are 'Concurrency Patterns' and how are they helpful?

From the very begining of the scientific study of concurrent programming, certain algorithms, even families of algorithms, were found to be key building blocks of reliable, debuggable programming. Originally these were described as algorithms in Algol60, pseudo-code or CSP, but now these are called 'patterns', so are often expressed in a "pattern language". The list of patterns has been extended, too.

The advantages obtained form this shift in language to the language of 'patterns' are mainly two: 1) wider acceptance and understanding of the algorithms because of the patterns movement and 2) deeper understanding of the patterns themselves, since a pattern really is more than just an algorithm.

Some examples: we now talk about the "Java Monitor Pattern", the "(Ad Hoc) Thread Confinement Pattern", "Thread/Object/Connection Pool", "Producer-Consumer Pattern", "work stealing", "encapsulating condition queues", "CAS(?)",

Many important concurrency patterns still do not have one widely agreed on name. An example is the well-known rule,

Every shared, mutable variable should be guarded by one lock. Make it clear to maintainers which lock that is.

Also important: along with the idea of design patterns, we now have a handy language for describing techniques that should never be used: 'anti-patterns'. The hack now called the "double checked lock" is a perfect example of a concurrency anti-pattern. Don't do this. It was once considered excusable when a certain feature of the JVM was usually broken, but now it only introduces problems.

Then there are other patterns that are not specific to concurrent programming, but together with the above patterns are often used to assemble the elementary building blocks of a concurrent program into larger thread-safe components. These include:


1.8   What are 'Condition Variables'?

Condition Variables are somewhat new to Java. They were first supported explicitly in JSE 5.0 with the Condition object.

But now that we have them, they are handy, so we should know what they are: they are variables used with a lock to allow more flexible scheduling than a bare lock/mutex/semaphore provides. That is, with a condition variable, we can now block a thread using the lock, blocking until the condition expressed by the variable is true

"But then how does this differ from a flag?" one may wonder. The difference is that the OS supports atomic access to the variable and lock together, so that using methods on the object does not introduce memory inconsistency problems.



1.13   What is special about Concurrency Programming in the Android environment?

All the special considerations for Concurrency Programmming in the Android environment follow from one key fact: the process hosting the thread may be killed at just about any time, due to OS Activity/Service lifecycle management.

Now this claim may sound a little surprising. After all, if the process is killed, one might expect that all the threads of that process will also be killed. "Doesn't this mean I don't have to worry about the threads?", one might wonder. The answer is, "not quite". You do not have to worry about them if you never hand a reference to this thread to any other thread, especially not to another process. But exceptions to this turn out to be a little surprising. You cannot, for example, count on all threads dying all at once when the process is killed. A more subtle error is when one thead keeps a reference to a thread in a process that has been killed.

As Meike says, "callbacks from other processes (Binder) happen on a different thread". Of course, this is also very important, important enough that it could be argued I should have listed two, not one "key facts" above. After all, this too is special to the Android environment

Another gotcha: AsyncTask itself relies on Executor

Android itself uses AsyncTask for Loaders, file access, etc (other slow stuff).

Meike lists 4 things to avoid:

  1. parameters that leak mutable state
  2. references to in-scope mutable state
  3. implicit references
  4. references to objects with different lifecycles