Multithreading and Synchronization 多线程和同步
Chapter 1 introduced the concept of the main thread, or UI thread, in which most events are handled处理了大多数线程. Even though you are not prevented from executing all your code from within the main thread。your application typically uses more than one thread. As a matter of fact, several threads are created and run as part of your application应用程序启动时也会创建并运行几个线程 even if you don’t create new threads yourself. For example, Eclipse’s DDMS perspective shows these threads when an application runs on an Android 3.1-based Galaxy Tab 10.1:
main
HeapWorker(执行finalize 函数和引用清理对象)
GC (Garbage Collector)
Signal Catcher(捕捉linux信号并进行处理)
JDWP (Java Debug Wire Protocol,调试协议服务)
Compiler(JIT COMPILER 即时编译器)
Binder Thread #1
Binder Thread #2
So far, we’ve discussed only the first one in the list, the main thread, so you probably were not expecting these extra seven. The good news is that you don’t have to worry about these other threads, which exist mostly for housekeeping都是管家的工作; besides, you don’t have much control over what they actually do. Your focus should be on the main thread and on not performing any long operation within that thread, to keep your application responsive让应用程序可以快速响应.
The particular housekeeping threads Android spawns depends on which Android
version an application is running on. For example, Android 2.1 generates six threads (as opposed to the eight listed above) because garbage collection takes place in a separate thread only in Android 2.3 and above, and the Just-In-Time compiler was not introduced until Android 2.2.
In this chapter you learn how to create your own threads, how to communicate between them, how objects can safely be shared between threads, and how, in general, you can tailor your code to take full advantage of your device’s multithreading capabilities. We also review common pitfalls陷阱 to avoid when working with threads in an Android application.
5.1 Threads
A Thread object, that is, an instance of the Thread class defined by Java, is a unit of execution with its own call stack自己带有调用栈的执行单位. Applications can create additional threads easily, as shown in Listing 5�C1. Of course, your application is free to create additional threads to perform some operations outside of the main thread; very often you will have to do exactly that to keep your application responsive.
Listing 5�C1. Creating Two Threads
// the run() method can simply be overridden…
Thread thread1 = new Thread("cheese1") {
@Override
public void run() {
Log.i(”thread1”, "I like Munster");
}
};
// …or a Runnable object can be passed to the Thread constructor
Thread thread2 = new Thread(new Runnable() {
public void run() {
Log.i(”thread2”, "I like Roquefort");
}
}, "cheese2");
// remember to call start() or else the threads won’t be spawned and nothing will
happen
thread1.start();
thread2.start();
Executing that code may actually give different results. Because each thread is a
separate unit of execution, and both threads have the same default priority, there is no guarantee “I like Munster” will be displayed before “I like Roquefort,” even though thread1 is started first. The actual result depends on the scheduling取决于系统调度的实现方式, which is implementation-dependent.
NOTE: A typical mistake is to call the run() method instead of start(). This causes the run() method from the Thread object (and the Runnable object if applicable) to be called from the current thread. In other words, no new thread would be generated.
The two threads above were simply started, with no expectation of a result transmitted back to the thread that spawned引发 them. While this is sometimes the desired effect, often you want to get some sort of result from what is being executed in different threads. For example, your application may want to compute a Fibonacci number in a separate thread, to keep the application responsive, but would want to update the user interface with the result of the computation. This scenario is shown in Listing 5�C2, where mTextView is a reference to a TextView widget in your layout and onClick is the method called when the user clicks on a button, or a view in general (see the android:onClick attribute in XML layout).
Listing 5�C2. Worker Thread to Compute Fibonacci Number
public void onClick (View v) {
new Thread(new Runnable() {
public void run() {
// note the ‘final’ keyword here (try removing it and see what happens)
final BigInteger f =
Fibonacci.recursiveFasterPrimitiveAndBigInteger(100000);
mTextView.post(new Runnable() {
public void run() {
mTextView.setText(f.toString());
}
});
}
}, “fibonacci”).start();
}
While this would work just fine, it is also quite convoluted复杂 and makes your code harder to read and maintain. You may be tempted to simplify the code from Listing 5�C2 and replace it with the code shown in Listing 5�C3. Unfortunately, this would be a bad idea as this would simply throw a CalledFromWrongThreadException exception, the reason being that the Android UI toolkit工具包 can be called only from the UI thread. The exception’s description says “only the original thread that created a view hierarchy视图树 can touch its views.” It is therefore mandatory强制 for the application to make sure TextView.setText() is called from the UI thread, for example by posting a Runnable object to the UI thread.
Listing 5�C3. Invalid Call From Non-UI Thread
public void onClick (View v) {
new Thread(new Runnable() {
public void run() {
BigInteger f = Fibonacci.recursiveFasterPrimitiveAndBigInteger(100000);
mTextView.setText(f.toString()); // will throw an exception
}
}, “fibonacci”).start();
}
TIP: To facilitate debugging, it is good practice to name the threads you spawn起个有意义的名字. If no name is specified, a new name will be generated. You can get the name of a thread by calling Thread.getName().
Each thread, regardless of how it was created, has a priority. The scheduler调度器 uses the priority to decide which thread to schedule for execution, that is, which thread gets to use the CPU. You can change the priority of a thread by calling Thread.setPriority(), as shown in Listing 5�C4.
Listing 5�C4. Setting a Thread’s Priority
Thread thread = new Thread("thread name") {
@Override
public void run() {
// do something here
}
};
thread.setPriority(Thread.MAX_PRIORITY); // highest priority (higher than UI thread)
thread.start();
If the priority is not specified, the default priority is used. The Thread class defines three
constants:
MIN_PRIORITY (1)
NORM_PRIORITY (5) �C the default priority
MAX_PRIORITY (10)
If your application attempts to set a thread’s priority to some out-of-range value, that is, less than 1 or greater than 10, then an IllegalArgumentException exception will be thrown.
Android provides another way to set a thread’s priority, based on Linux priorities, with the Process.setThreadPriority APIs in the android.os package. The following eight priorities are defined:
THREAD_PRIORITY_AUDIO (-16)
THREAD_PRIORITY_BACKGROUND (10)
THREAD_PRIORITY_DEFAULT (0)
THREAD_PRIORITY_DISPLAY (-4)
THREAD_PRIORITY_FOREGROUND (-2)
THREAD_PRIORITY_LOWEST (19)
THREAD_PRIORITY_URGENT_AUDIO (-19)
THREAD_PRIORITY_URGENT_DISPLAY (-8)
You can also use the Process.THREAD_PRIORITY_LESS_FAVORABLE (+1) and
Process.THREAD_PRIORITY_MORE_FAVORABLE as increments (-1). For example to set a thread’s priority to a slightly higher priority than default, you could set the priority to (THREAD_PRIORITY_DEFAULT + THREAD_PRIORITY_MORE_FAVORABLE).
TIP: Use THREAD_PRIORITY_LESS_FAVORABLE and THREAD_PRIORITY_MORE_FAVORABLE instead of +1 and -1 so you won’t have to remember whether a higher number means lower of higher priority. Also, avoid mixing calls to Thread.setPriority and Process.setThreadPriority as this
could make your code confusing. Note that Linux priorities go from -20 (highest) to 19 (lowest) whereas Thread priorities go from 1 (lowest) to 10 (highest).
Be very careful when you decide to change the priority of your threads. Increasing the priority of one thread may result in a faster execution of this particular thread but may negatively impact the other threads, which may not get access to the CPU resource as quickly as they should, therefore disrupting the user experience as a whole. Consider implementing a priority aging algorithm if it makes sense for your application.
Even though creating a thread to perform a background task is trivial in Android, as demonstrated in Listing 5�C1, updating the user interface can be quite tedious麻烦: it requires posting the result back to the main thread because calling any View method must be done from the UI thread.
5.2 AsyncTask
Very often, your application has to deal with the sequence that was shown in Listing 5�C2:
Event is received in UI thread
Operation is to be executed in non-UI thread in response to event
UI needs to be updated with result of operation(UI根据处理结果进行刷新)
To simplify this common pattern, Android defines the AsyncTask class in Android 1.5 and above. The AsyncTask class allows your application to easily perform a background operation and publish the result in the UI thread. Threads, Runnables, and other related objects are hidden from you for simplicity为了简单起见. Listing 5�C5 shows how you would implement the sequence from Listing 5�C2 using the AsyncTask class.
Listing 5�C5. Using AsyncTask
public void onClick (View v) {
// AsyncTask<Params, Progress, Result> anonymous class
new AsyncTask<Integer, Void, BigInteger>() {
@Override
protected BigInteger doInBackground(Integer... params) {
return Fibonacci.recursiveFasterPrimitiveAndBigInteger(params[0]);
}
@Override
protected void onPostExecute(BigInteger result) {
mTextView.setText(result.toString());
}
}.execute(100000);
}
Since doInBackground() is an abstract method, it has to be implemented. While you don’t have to override onPostExecute(), it is likely you will since one of the main purposes of AsyncTask is to let you publish the result to the UI thread. The following AsyncTask protected methods are all called from the UI thread:
onPreExecute()
onProgressUpdate(Progress… values)
onPostExecute(Result result)
onCancelled()
onCancelled(Result result) (API introduced in Android 3.0)
The onProgressUpdate() method is called when publishProgress() is called from within doInBackground(). This method allows you to do things like update the UI as the background operations are progressing. A typical example would be to update a
progress bar as a file is being downloaded in the background. Listing 5�C6 shows how
multiple files can be downloaded.
Listing 5�C6. Downloading Multiple Files
AsyncTask<String, Object, Void> task = new AsyncTask<String, Object, Void>() {
private ByteArrayBuffer downloadFile(String urlString, byte[] buffer) {
try {
URL url = new URL(urlString);
URLConnection connection = url.openConnection();
InputStream is = connection.getInputStream();
//Log.i(TAG, "InputStream: " + is.getClass().getName()); // if you are
curious
//is = new BufferedInputStream(is); // optional line, try with and without
ByteArrayBuffer baf = new ByteArrayBuffer(640 * 1024);
int len;
while ((len = is.read(buffer)) != -1) {
baf.append(buffer, 0, len);
}
return baf;
} catch (MalformedURLException e) {
return null;
} catch (IOException e) {
return null;
}
}
@Override
protected Void doInBackground(String... params) {
if (params != null && params.length > 0) {
byte[] buffer = new byte[4 * 1024]; // try different sizes (1 for example
will give lower performance)
for (String url : params) {
long time = System.currentTimeMillis();
ByteArrayBuffer baf = downloadFile(url, buffer);
time = System.currentTimeMillis() - time;
publishProgress(url, baf, time);
}
} else {
publishProgress(null, null);
}
return null; // we don’t care about any result but we still have to return
something
}
@Override
protected void onProgressUpdate(Object... values) {
// values[0] is the URL (String), values[1] is the buffer (ByteArrayBuffer),
values[2] is the duration
String url = (String) values[0];
ByteArrayBuffer buffer = (ByteArrayBuffer) values[1];
if (buffer != null) {
long time = (Long) values[2];
Log.i(TAG, "Downloaded " + url + " (" + buffer.length() + " bytes) in " +
time + " milliseconds");
} else {
Log.w(TAG, "Could not download " + url);
}
// update UI accordingly, etc
}
};
String url1 = "http://www.google.com/index.html";
String url2 = "http://d.android.com/reference/android/os/AsyncTask.html";
task.execute(url1, url2);
//task.execute("http://d.android.com/resources/articles/painless-threading.html"); //
try that to get exception
The example in Listing 5�C6 simply downloads the files in memory (a ByteArrayBuffer object). If you want to save the file to permanent storage, you should also perform that operation in a thread other than the UI thread. In addition, the example showed files downloaded one after the other. Depending on your application’s needs, it may be better to download several files in parallel并行.
NOTE: An AsyncTask object must be created in the UI thread and can be executed only once.
When the doInBackground() task is actually scheduled depends on the Android version. Before Android 1.6, tasks were executed serially, so only one background thread was needed. Starting with Android 1.6, the single background thread was replaced by a pool of threads allowing multiple tasks to be executed in parallel to allow for better performance. However, executing several tasks in parallel can cause serious problems when synchronization is not implemented properly or when tasks are executed or completed in a certain order (which may not be the order the developer anticipated).Consequently, the Android team plans to revert back to a single background thread model by default after Honeycomb. To continue to allow applications to execute tasks in parallel, a new executeOnExecutor() API was added in Honeycomb, providing time for application developers to update their applications accordingly. This new API can be used together with AsyncTask.SERIAL_EXECUTOR for serial execution or AsyncTask.THREAD_POOL_EXECUTOR for parallel execution.
The planned future change shows that parallel execution requires a careful design and thorough tests. The Android team may have underestimated the potential problems or overestimated the applications’ abilities to deal with them when switching to a pool of threads in Android 1.6, triggering the decision to revert back to a single thread model after Honeycomb. Applications’ overall quality will improve while more experienced developers will still have the flexibility to execute tasks in parallel for better performance. The AsyncTask class can simplify your code when dealing with background tasks and user-interface updates简化后台任务和后台界面更新代码, however it is not meant to fully replace the more basic classes Android defines to communicate between threads.
5.3 Handlers and Loopers
Android defines two classes in the android.os package that will often be the
cornerstones基石 of the interthread communication in your multithreaded applications:
Handler
Looper
While creating an AsyncTask object hides the Handler and Looper details from you, in some cases you need to use handlers and loopers explicitly明白,确定的, for example when you need to post a Runnable to a thread other than the main thread.
5.3.1Handlers
Listing 5�C2 gave you a glimpse of how the Handler and Looper work together: you use a Handler object to post a Runnable in a Looper’s message queue. Your application’s main thread already has a message queue, so you don’t have to create one explicitly. However, the threads you create do not come automatically with a message queue and message loop, so you would have to create one yourself if needed. Listing 5�C7 shows how you can create a thread with a Looper.
Listing 5�C7. Thread Class With Message Queue
public class MyThread extends Thread {
private static final String TAG = “MyThread”;
private Handler mHandler;
public MyThread(String name) {
super(name);
}
public Handler getHandler() {
return mHandler;
}
@Override
public void run() {
Looper.prepare(); // binds a looper to this thread
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
// process messages here
}
}
};
// the handler is bound to this thread’s looper
Looper.loop(); // don’t forget to call loop() to start the message loop
// loop() won’t return until the loop is stopped (e.g., when Looper.quit() is
called)
}
}
NOTE: The handler object is created in the run() method as it needs to be bound to a specific looper, which is also created in run() when Looper.prepare() is called. Consequently, calling getHandler() before the thread is spawned引起 will return null.
Once the thread is running, you can post Runnable objects or send messages to its
message queue, as shown in Listing 5�C8.
Listing 5�C8. Posting Runnables and Sending Messages
MyThread thread = new MyThread(“looper thread”);
thread.start();
// later...
Handler handler = thread.getHandler();
// careful: this could return null if the handler is not initialized yet!
// to post a runnable
handler.post(new Runnable() {
public void run() {
Log.i(TAG, "Where am I? " + Thread.currentThread().getName());
}
});
// to send a message
int what = 0; // define your own values
int arg1 = 1;
int arg2 = 2;
Message msg = Message.obtain(handler, what, arg1, arg2);
handler.sendMessage(msg);
// another message...
what = 1;
msg = Message.obtain(handler, what, new Long(Thread.currentThread().getId()));
handler.sendMessageAtFrontOfQueue(msg);
TIP: Use one of the Message.obtain() or Handler.obtainMessage() APIs to get a
Message object, as they return a Message object from the global message pool全局消息池, which is more efficient than allocating a new instance every single time a message is needed. These APIs also make it simpler to set the various fields of the message.
5.3.2 Loopers
Android provides an easier way to work with looper threads with the HandlerThread class, which also makes it easier to avoid the potential race condition避免潜在竞争状况 mentioned in Listing 5�C8, where getHandler() may still return null even after the thread has been started. Listing 5�C9 shows how to use the HandlerThread class.
Listing 5�C9. Using the HandlerThread Class
public class MyHandlerThread extends HandlerThread {
private static final String TAG = "MyHandlerThread";
private Handler mHandler;
public MyHandlerThread(String name) {
super(name);
}
public Handler getHandler() {
return mHandler;
}
@Override
public void start() {
super.start();
Looper looper = getLooper(); // will block until thread’s Looper object is
initialized
mHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
// process messages here
}
}
};
}
}
Since the handler is created in the start() method instead of in the run() method, it will be available (via a call to getHandler()) after the start() method returns without any race condition.
5.4 Data Types
We have seen two ways to spawn产生 a thread, using the Thread and AsyncTask classes. When two or more threads access the same data有两个或者多个线程访问相同的数据, you need to make sure the data types support concurrent access. The Java language defines many classes in the java.util.concurrent package for that purpose:
ArrayBlockingQueue
ConcurrentHashMap
ConcurrentLinkedQueue
ConcurrentSkipListMap
ConcurrentSkipListSet
CopyOnWriteArrayList
CopyOnWriteArraySet
DelayQueue
LinkedBlockingDeque
LinkedBlockingQueue
PriorityBlockingQueue
SynchronousQueue
You will have to carefully select your data types based on your application’s
requirements. Also, the fact that they are concurrent implementations并发实现 does not necessarily imply that operations are atomic. In fact, many operations are not atomic and by design are not meant to be. For example, the putAll() method in the
ConcurrentSkipListMap class is not atomic. A concurrent implementation merely means that the data structure will not be corrupted when accessed by multiple threads.
Synchronized同步, Volatile异变, Memory Model内存模型
If you want to share objects between multiple threads but have not implemented any fine-grained细粒度 locking mechanism, you can use the synchronized keyword to make sure your access is thread-safe, as shown in Listing 5�C10. Listing 5�C10. Using the Synchronized Keyword
public class MyClass {
private int mValue;
public MyClass(int n) {
mValue = n;
}
public synchronized void add (int a) {
mValue += a;
}
public synchronized void multiplyAndAdd (int m, int a) {
mValue = mValue * m + a;
}
}
The two methods add and multiplyAndAdd in Listing 5�C7 are synchronized methods. This means two things:
If one thread is executing a synchronized method, other threads trying to call any synchronized method for the same object have to wait until the first thread is done.
When a synchronized method exits, the updated state of the object is visible to all other threads.
The first item is quite intuitive直观. The second one should be as well although it still requires an explanation. As a matter of fact, the Java memory model is such that a modification to a variable in one thread may not immediately be visible to other threads. Actually, it may never be visible. Consider the code in Listing 5�C11: if one thread calls MyClass.loop(), and at some point in the future another thread calls
Myclass.setValue(100), the first thread may still not terminate; may carry on looping
forever and always print out a value other than 100, simply because of the Java
language’s memory model.
Listing 5�C11. Java Memory Model Impact
public class MyClass {
private static final String TAG = "MyClass";
private static int mValue = 0;
public static void setValue(int n) {
mValue = n;
}
public static void loop () {
while (mValue != 100) {
try {
Log.i(TAG, “Value is ” + mValue);
Thread.sleep(1000);
} catch (Exception e) {
// ignored
}
}
}
}
You have two options to fix that:
Use the synchronized keyword, as shown in Listing 5�C12.
Use the volatile keyword, as shown in Listing 5�C13.
Listing 5�C12. Adding the Synchronized Keyword
public class MyClass {
private static final String TAG = "MyClass";
private static int mValue = 0;
public static synchronized void setValue(int n) {
mValue = n;
}
public static synchronized int getValue() {
return mValue;
}
public static void loop () {
int value;
while ((value = getValue()) != 100) {
try {
Log.i(TAG, “Value is ” + value);
Thread.sleep(1000);
} catch (Exception e) {
// ignored
}
}
}
}
Listing 5�C13. Adding the Volatile Keyword
public class MyClass {
private static final String TAG = "MyClass";
private static volatile int mValue = 0; // we add the volatile keyword and remove
the synchronize keyword
public static void setValue(int n) {
mValue = n; // you’d still have to use synchronized if that statement were
mValue += n (not atomic)
}
public static void loop () {
while (mValue != 100) {
try {
Log.i(TAG, “Value is ” + mValue);
Thread.sleep(1000);
} catch (Exception e) {
// ignored
}
}
}
}
NOTE: Make sure you understand which statements are atomic. For example, value++ is not atomic while value = 1 is. This is important as the volatile keyword can only fix concurrency issues if the statements using the variable are atomic. If they are not, then you have to use the synchronized keyword instead.
You can improve concurrency and throughput by using synchronized statements, as shown in Listing 5�C14, as opposed to making whole methods synchronized. In these cases, you want to protect only the part that needs to be protected (that is, where mValue is being modified), but leave the log message outside of the synchronized block.
You can also use objects other than this as a lock.
Listing 5�C14. Synchronized Statements
public class MyOtherClass {
private static final String TAG = "MyOtherClass";
private int mValue;
private Object myLock = new Object(); // more than this… there is
public MyClass(int n) {
mValue = n;
}
public void add (int a) {
synchronized (myLock) {
mValue += a;
}
Log.i(TAG, "add: a=" + a); // no need to block
}
public void multiplyAndAdd (int m, int a) {
synchronized (myLock) {
mValue = mValue * m + a;
}
Log.i(TAG, " multiplyAndAdd: m=" + m + ", a=" + a); // no need to block
}
}
Making methods or statements synchronized is the easiest way to guarantee your class supports concurrent access. However, it may reduce the throughput when not
everything needs to be protected, and even worse, it can cause deadlocks. Indeed,
deadlocks can occur when you call another object’s method from within a synchronized block, which may attempt to acquire a lock on an object that is already locked and waiting for your own object’s lock.
TIP: Don’t call another object’s method within a synchronized block unless you can guarantee no deadlock will occur. Usually, you can guarantee that only when you are the author of the code of the other object’s class.
In general一般情况下, it is best to avoid accessing the same object from different threads if you have any doubt about whether it will work or not. The classes defined by Java are a good reference, and since the Android code is available, you can refer to the implementation of these classes to understand the various techniques you can take advantage of. In addition, to simplify your development, Java defines many classes that already support thread-safe or concurrent programming, which can be used as the basis for some of your own algorithms.
5.5 Concurrency 并发
More classes are defined in the java.util.concurrent.atomic and java.util.concurrent.locks
packages. The java.util.concurrent.atomic package contains the following classes:
AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicIntegerFieldUpdater (abstract)
AtomicLong
AtomicLongArray
AtomicLongFieldUpdater (abstract)
AtomicMarkableReference
AtomicReference
AtomicReferenceArray
AtomicReferenceFieldUpdater (abstract)
AtomicStampedReference
Most of these classes require little explanation as they simply define methods to update values atomically. For example, the AtomicInteger class defines the addAndGet() method, which adds a given value to the current value of the AtomicInteger object while returning the updated value. The abstract classes defined in this package are used internally but would very rarely be used directly in your applications’ code.
I n addition to the CountDownLatch, CyclicBarrier, and Semaphore classes from the
java.util.concurrent package, more synchronization aids are defined in the
java.util.concurrent.locks package:
AbstractOwnableSynchronizer (abstract, since API level 5)
AbstractQueuedLongSynchronizer (abstract, since API level 9)
AbstractQueuedLongSynchronizer (since API level 9)
AbstractQueuedSynchronizer (abstract)
AbstractQueuedSynchronizer.ConditionObject
LockSupport
ReentrantLock
ReentrantReadWriteLock
ReentrantReadWriteLock.ReadLock
ReentrantReadWriteLock.WriteLock
These classes are not commonly used in typical Android applications. Perhaps the most common one you would still use is the ReentrantReadWriteLock class, together with its ReentrantReadWriteLock.ReadLock and ReentrantReadWriteLock.WriteLock
companions, as they allow for multiple reader threads to have access to the data (as
long as there is no writer thread modifying the data) while there can only be one writer thread at a time. This is a common object when multiple threads access the same data for reading only, and you want to maximize throughput.
As a general rule, sharing data between threads creates problems (throughput吞吐量, concurrency issues). Synchronization can become quite complex, and you need to have a solid understanding of the subject and of your code to enable shared data
successfully. Debugging issues related to synchronization can also be quite an
endeavor, so you should aim for simplicity before you try to optimize things like
throughput. Focus on your application’s quality first before any optimization注重质量应胜于优化.
5.6 Multicore多核
Recently, a number of Android devices came out based on multicore architecture基于多核架构. For example, the Samsung Galaxy Tab 10.1 and Motorola Xoom tablets both use a dualcore processor双核处理器 (Cortex A9 cores). A multicore processor, unlike a single-core processor, can execute multiple threads simultaneously. That being said, it is easy to see how this could improve performance as a dual-core processor can theoretically do twice as much as a single-core one (everything else being equal, for example, clock frequency).
Although optimizing for multiple cores is not as easy as it sounds, and some caveats exist, your application can definitely leverage the additional power that today’s multicore processors bring. Devices with dual-core CPUs include:
Samsung Galaxy Tab 10.1
Motorola Xoom
Motorola Phonton 4G
Motorola Droid 3
HTC EVO 3D
LG Optimus 2X
Samsung Galaxy Nexus
In many cases, you won’t have to worry about how many cores a device has. Delegating certain operations to a separate thread using a Thread object or AsyncTask is usually enough as you can still create multiple threads even on a single-core processor. If the processor has several cores, then threads will simply run on different processor units, which will be transparent to your application对你的应用是透明的.
That being said, there may be times when you really need to get the most out of the CPU to achieve an acceptable level of performance and design algorithms especially tailored for multiple cores.
To achieve the best performance, your application may first need to find out how many cores are available, simply by calling the RunTime.availableProcessors() method, as shown in Listing 5�C15.
Listing 5�C15. Getting the Number Of Processors
// will return 2 on a Galaxy Tab 10.1 or BeBox Dual603, but only 1 on a Nexus S or
Logitech Revue
final int proc = Runtime.getRuntime().availableProcessors();
Typically, the number of “available processors” is 1 or 2 although future products will be using quad-core CPUs. Current Android notebooks may already be using quad-core architecture. Depending on when you plan on making your application available, you may want to focus on only 1- and 2-core CPUs and only later publish an update to take advantage of more cores.
NOTE: Assume the number of cores may not always be a power of 2.
5.6.1 Modifying Algorithm For Multicore
Some of the Fibonacci algorithms presented in Chapter 1 are good candidates to take advantage of multiple cores. Let’s start with the divide-and-conquer algorithm whose implementation is shown in Listing 5�C16 (which is the same implementation shown in
Listing 1-7 in Chapter 1).
Listing 5�C16. Fibonacci Divide-and-Conquer Algorithm
public class Fibonacci
{
public static BigInteger recursiveFasterBigInteger (int n)
{
if (n > 1) {
int m = (n / 2) + (n & 1);
// two simpler sub-problems
BigInteger fM = recursiveFasterBigInteger(m);
BigInteger fM_1 = recursiveFasterBigInteger(m - 1);
// results are combined to compute the solution to the original problem
if ((n & 1) == 1) {
// F(m)^2 + F(m-1)^2
return fM.pow(2).add(fM_1.pow(2));
} else {
// (2*F(m-1) + F(m)) * F(m)
return fM_1.shiftLeft(1).add(fM).multiply(fM);
}
}
return (n == 0) ? BigInteger.ZERO : BigInteger.ONE;
}
}
This algorithm does what divide-and-conquer algorithms do:
The original problem is divided into simpler sub-problems.
The results are then combined to compute the solution to the
original problem. Since the two sub-problems are independent, it is possible to execute them in parallel without much synchronization. The Java language defines the ExecutorService interface, which is implemented by several classes you can use to schedule work to be done. An example is shown in Listing 5�C17, which uses the factory method from the
Executors class to create a thread pool.
Listing 5�C17. Using ExecutorService
public class Fibonacci {
private static final int proc = Runtime.getRuntime().availableProcessors();
private static final ExecutorService executorService =
Executors.newFixedThreadPool(proc + 2);
public static BigInteger recursiveFasterBigInteger (int n) {
// see Listing 5�C16 for implementation
}
public static BigInteger recursiveFasterBigIntegerAndThreading (int n) {
int proc = Runtime.getRuntime().availableProcessors();
if (n < 128 || proc <= 1) {
return recursiveFasterBigInteger(n);
}
final int m = (n / 2) + (n & 1);
Callable<BigInteger> callable = new Callable<BigInteger>() {
public BigInteger call() throws Exception {
return recursiveFasterBigInteger(m);
}
};
Future<BigInteger> ffM = executorService.submit(callable); // submit first job
as early as possible
callable = new Callable<BigInteger>() {
public BigInteger call() throws Exception {
return recursiveFasterBigInteger(m-1);
}
};
Future<BigInteger> ffM_1 = executorService.submit(callable); // submit second
job
// getting partial results and combining them
BigInteger fM, fM_1, fN;
try {
fM = ffM.get(); // get result of first sub-problem (blocking call)
} catch (Exception e) {
// if exception, compute fM in current thread
fM = recursiveFasterBigInteger(m);
}
try {
fM_1 = ffM_1.get(); // get result of second sub-problem (blocking call)
} catch (Exception e) {
// if exception, compute fM in current thread
fM_1 = recursiveFasterBigInteger(m-1);
}
if ((n & 1) != 0) {
fN = fM.pow(2).add(fM_1.pow(2));
} else {
fN = fM_1.shiftLeft(1).add(fM).multiply(fM);
}
return fN;
}
}
As you can clearly see, the code is harder to read. Moreover, this implementation is still based on a low-performance code: as we saw in Chapter 1, the two sub-problems would end up computing many of the same Fibonacci numbers. Better implementations were using a cache to remember the Fibonacci numbers already computed, saving significant time. Listing 5�C18 shows a very similar implementation, but using a cache.
Listing 5�C18. Using ExecutorService and Caches
public class Fibonacci {
private static final int proc = Runtime.getRuntime().availableProcessors();
private static final ExecutorService executorService =
Executors.newFixedThreadPool(proc + 2);
private static BigInteger recursiveFasterWithCache (int n, Map<Integer, BigInteger>
cache)
{
// see Listing 1-11 for implementation (slightly different though as it was
using SparseArray)
}
public static BigInteger recursiveFasterWithCache (int n)
{
HashMap<Integer , BigInteger> cache = new HashMap<Integer , BigInteger>();
return recursiveFasterWithCache(n, cache);
}
public static BigInteger recursiveFasterWithCacheAndThreading (int n) {
int proc = Runtime.getRuntime().availableProcessors();
if (n < 128 || proc <= 1) {
return recursiveFasterWithCache (n);
}
final int m = (n / 2) + (n & 1);
Callable<BigInteger> callable = new Callable<BigInteger>() {
public BigInteger call() throws Exception {
return recursiveFasterWithCache (m);
}
};
Future<BigInteger> ffM = executorService.submit(callable);
callable = new Callable<BigInteger>() {
public BigInteger call() throws Exception {
return recursiveFasterWithCache (m-1);
}
};
Future<BigInteger> ffM_1 = executorService.submit(callable);
// getting partial results and combining them
BigInteger fM, fM_1, fN;
try {
fM = ffM.get(); // get result of first sub-problem (blocking call)
} catch (Exception e) {
// if exception, compute fM in current thread
fM = recursiveFasterBigInteger(m);
}
try {
fM_1 = ffM_1.get(); // get result of second sub-problem (blocking call)
} catch (Exception e) {
// if exception, compute fM in current thread
fM_1 = recursiveFasterBigInteger(m-1);
}
if ((n & 1) != 0) {
fN = fM.pow(2).add(fM_1.pow(2));
} else {
fN = fM_1.shiftLeft(1).add(fM).multiply(fM);
}
return fN;
}
}
5.6.2 Using Concurrent Cache使用并发缓存
One thing to notice in this implementation is the fact that each sub-problem will use its own cache object and therefore duplicate values will still be computed. For the two subproblems to share a cache, we need to change the cache from a SparseArray object to an object that allows concurrent access from different threads. Listing 5�C19 shows such an implementation, using a ConcurrentHashMap object as the cache.
Listing 5�C19. Using ExecutorService and a Single Cache
public class Fibonacci {
private static final int proc = Runtime.getRuntime().availableProcessors();
private static final ExecutorService executorService =
Executors.newFixedThreadPool(proc + 2);
private static BigInteger recursiveFasterWithCache (int n, Map<Integer, BigInteger>
cache)
{
// see Listing 1-11 for implementation (slightly different though as it was
using SparseArray)
}
public static BigInteger recursiveFasterWithCache (int n)
{
HashMap<Integer , BigInteger> cache = new HashMap<Integer , BigInteger>();
return recursiveFasterWithCache(n, cache);
}
public static BigInteger recursiveFasterWithCacheAndThreading (int n) {
int proc = Runtime.getRuntime().availableProcessors();
if (n < 128 || proc <= 1) {
return recursiveFasterWithCache (n);
}
final ConcurrentHashMap<Integer, BigInteger> cache =
new ConcurrentHashMap<Integer, BigInteger>(); // concurrent access ok
final int m = (n / 2) + (n & 1);
Callable<BigInteger> callable = new Callable<BigInteger>() {
public BigInteger call() throws Exception {
return recursiveFasterWithCache (m, cache); // first and second jobs
share the same cache
}
};
Future<BigInteger> ffM = executorService.submit(callable);
callable = new Callable<BigInteger>() {
public BigInteger call() throws Exception {
return recursiveFasterWithCache (m-1, cache); // first and second jobs
share the same cache
}
};
Future<BigInteger> ffM_1 = executorService.submit(callable);
// getting partial results and combining them
BigInteger fM, fM_1, fN;
try {
fM = ffM.get(); // get result of first sub-problem (blocking call)
} catch (Exception e) {
// if exception, compute fM in current thread
fM = recursiveFasterBigInteger(m);
}
try {
fM_1 = ffM_1.get(); // get result of second sub-problem (blocking call)
} catch (Exception e) {
// if exception, compute fM in current thread
fM_1 = recursiveFasterBigInteger(m-1);
}
if ((n & 1) != 0) {
fN = fM.pow(2).add(fM_1.pow(2));
} else {
fN = fM_1.shiftLeft(1).add(fM).multiply(fM);
}
return fN;
}
}
NOTE: The second parameter of recursiveFasterWithCache is a map so that it can be called with any cache that implements the Map interface, for example a ConcurrentHashMap or HashMap object. A SparseArray object is not a map.
You may not always observe performance gains when dividing a problem into subproblems and assigning each sub-problem to a different thread. Since there could still be dependency between data, and synchronization would have to occur, threads may spend some or most of their time waiting for the access to data to be possible. Also, performance gains may not be as significant as you would hope for. Even thoughtheoretically you would expect to double the performance on a dual-core processor and quadruple the performance on a quad-core processor, reality can show you otherwise.
In practice, it is easier to use multiple threads to perform unrelated tasks现实中最好使用多线程执行无关任务 (therefore avoiding the need for synchronization), or tasks that need to be synchronized only either sporadically零星 or regularly if the frequency is “low enough.” For example, a video game would typically use one thread for the game logic and another thread to do the rendering. The rendering thread would therefore need to read the data manipulated by
the logic thread 30 or 60 times per second (for every frame being rendered), and could relatively quickly make a copy of the data needed to start rendering a frame渲染一帧所需的数据, therefore blocking the access for only a very short moment.
5.7 Activity Lifecycle 生命周期
The threads you create are not automatically aware of the changes in your activity’s lifecycle. For example, a thread you spawned would not automatically be notified that your activity’s onStop() method has been called, and the activity is not visible anymore, or that your activity’s onDestroy() method has been called. This means you may need to do additional work to synchronize your threads with your application’s lifecycle让你的线程和生命周期保持同步. Listing 5�C20 shows a simply example of an AsyncTask still running even after the activity has
been destroyed.
Listing 5�C20. Computing a Fibonacci Number In the Background Thread and Updating the User Interface
Accordingly public class MyActivity extends Activity {
private TextView mResultTextView;
private Button mRunButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// layout contains TextView and Button
mResultTextView = (TextView) findViewById(R.id.resultTextView); // where result
will be displayed
mRunButton = (Button) findViewById(R.id.runButton); // button to start
computation
}
public void onClick (View v) {
new AsyncTask<Integer, Void, BigInteger>() {
@Override
protected void onPreExecute() {
// button is disabled so the user can only start one computation at a
time
mRunButton.setEnabled(false);
}
@Override
protected void onCancelled() {
// button is enabled again to let the user start another computation
mRunButton.setEnabled(true);
}
@Override
protected BigInteger doInBackground(Integer... params) {
return Fibonacci.recursiveFasterPrimitiveAndBigInteger(params[0]);
}
@Override
protected void onPostExecute(BigInteger result) {
mResultTextView.setText(result.toString());
// button is enabled again to let the user start another computation
mRunButton.setEnabled(true);
}
}.execute(100000); // for simplicity here, we hard-code the parameter
}
}
This example does two simple things when the user presses the button:
It computes a Fibonacci number in a separate thread.
The button is disabled while the computation is ongoing and enabled once the computation is completed so that the user can start only one computation at a time.
On the surface, it looks correct. However, if the user turns the device while the
computation is being executed如果用户在计算期间旋转了屏幕, the activity will be destroyed and created again. (We assume here that the manifest file does not specify that this activity will handle the orientation change by itself.) The current instance of MyActivity goes through the usual sequence of onPause(), onStop(), and onDestroy() calls, while the new instance goes through the usual sequence of onCreate(), onStart(), and onResume() calls. While all of this is happening, the AsyncTask’s thread still runs as if nothing had happened, unaware of the orientation change, and the computation eventually completes. Again, it looks
correct so far, and this would seem to be the behavior one would expect.
However, one thing happened that you may not have been expecting: the button
became enabled again after the change of orientation was completed. This is easily
explained since the Button you see after the change of orientation is actually a new
button, which was created in onCreate() and is enabled by default. As a onsequence,
the user could start a second computation while the first one is still going. While
relatively harmless相对无害, this breaks the user-interface paradigm you had established when deciding to disable the button while a computation is ongoing.
5.7.1 Passing Information 传递信息
If you want to fix this bug, you may want the new instance of the activity to know
whether a computation is already in progress so that is can disable the button after it is created in onCreate(). Listing 2-21 shows the modifications you could make to
communicate information to the new instance of MyActivity.
Listing 5�C21. Passing Information From One Activity Instance to Another
public class MyActivity extends Activity {
private static final String TAG = “MyActivity”;
private TextView mResultTextView;
private Button mRunButton;
private AsyncTask<Integer, Void, BigInteger> mTask; // we’ll pass that object to the
other instance
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// we add a log message here to know what instance of MyActivity is now created
Log.i(TAG, “MyActivity instance is ” + MyActivity.this.toString());
Log.i(TAG, “onCreate() called in thread ” + Thread.currentThread().getId());
// layout contains TextView and Button
mResultTextView = (TextView) findViewById(R.id.resultTextView); // where result
will be displayed
mRunButton = (Button) findViewById(R.id.runButton); // button to start
computation
// we get the object returned in onRetainNonConfigurationInstance() below
mTask = (AsyncTask<Integer, Void, BigInteger>)
getLastNonConfigurationInstance();
if (mTask != null) {
mRunButton.setEnabled(false); // computation still in progress so we disable
the button
}
}
@Override
public Object onRetainNonConfigurationInstance() {
return mTask; // will be non-null if computation is in progress
}
public void onClick (View v) {
// we keep a reference to the AsyncTask object
mTask = new AsyncTask<Integer, Void, BigInteger>() {
@Override
protected void onPreExecute() {
CHAPTER 5: Multithreading and Synchronization 157
// button is disabled so the user can start only one computation at a
time
mRunButton.setEnabled(false);
}
@Override
protected void onCancelled() {
// button is enabled again to let the user start another computation
mRunButton.setEnabled(true);
mTask = null;
}
@Override
protected BigInteger doInBackground(Integer... params) {
return Fibonacci.recursiveFasterPrimitiveAndBigInteger(params[0]);
}
@Override
protected void onPostExecute(BigInteger result) {
mResultTextView.setText(result.toString());
// button is enabled again to let the user start another computation
mRunButton.setEnabled(true);
mTask = null;
// we add a log message to know when the computation is done
Log.i(TAG, “Computation completed in ” + MyActivity.this.toString());
Log.i(TAG, “onPostExecute () called in thread ” +
Thread.currentThread().getId());
}
}.execute(100000); // for simplicity here, we hard-code the parameter
}
}
NOTE: onRetainNonConfigurationInstance() is now deprecated in favor of the Fragment APIs available in API level 11 or on older platforms through the Android compatibility package.This deprecated method is used here for simplicity; you will find more sample code using this method. However, you should write new applications using the Fragment APIs.
If you execute that code, you’ll then find that the button remains disabled when you rotate旋转 your device and a computation is in progress. This would seem to fix the problem we encountered in Listing 5�C20. However, you may notice a new problem: if you rotate the device while a computation is in progress and wait until the computation is done, the button is not enabled again等计算完成按钮也不会被启用 even though onPostExecute() was called. This is a much more significant problem since the button can never be enabled again! Moreover, the
result of the computation is not propagated on the user interface. (This problem is also in Listing 5�C20, so probably you would have noticed that issue before the fact that the button was enabled again after the orientation change.)
Once again this can easily be explained (but may not be obvious if you are relatively new to Java): while onPostExecute was called in the same thread as onCreate (the first activity was destroyed but the main thread is still the same), the mResultTextView and mRunButton objects used in onPostExecute actually belong to the first instance of MyActivity, not to the new instance. The anonymous inner class declared when the new AsyncTask object was created is associated with the instance of its enclosing class (在AsyncTast创建时匿名内部类,声明的匿名内部类与外部类发生关联)(this is why the AsyncTask object we created can reference the fields declared in MyActivity such as mResultTextView and mTask), and therefore it won’t have access to the fields of
the new instance of MyActivity. Basically, the code in Listing 5�C21 has two major flaws缺陷 when the user rotates the device while a computation is in progress:
The button is never enabled again, and the result is never showed.
The previous activity is leaked since mTask keeps a reference to an instance of its enclosing class (so two instances of MyActivity exist when the device is rotated旋转后存在两个MyActivity实例).
5.7.2 Remembering State记住状态
One way to solve this problem is to simply let the new instance of MyActivity know that a computation was in progress and to start this computation again. The previous computation can be canceled in onStop() or onDestroy() using the AsyncTask.cancel()
API. Listing 5�C22 shows a possible implementation.
Listing 5�C22. Remembering a Computation In Progress
public class MyActivity extends Activity {
private static final String TAG = “MyActivity”;
private static final String STATE_COMPUTE = “myactivity.compute”;
private TextView mResultTextView;
private Button mRunButton;
private AsyncTask<Integer, Void, BigInteger> mTask;
@Override
protected void onStop() {
super.onStop();
if (mTask != null) {
mTask.cancel(true); // although it is canceled now, the thread may still be
running for a while
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// if called, it is guaranteed to be called before onStop()
super.onSaveInstanceState(outState);
if (mTask != null) {
outState.putInt(STATE_COMPUTE, 100000); // for simplicity, hard-coded value
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// we add a log message here to know what instance of MyActivity is now created
Log.i(TAG, “MyActivity instance is ” + MyActivity.this.toString());
Log.i(TAG, “onCreate() called in thread ” + Thread.currentThread().getId());
// layout contains TextView and Button
mResultTextView = (TextView) findViewById(R.id.resultTextView); // where result
will be displayed
mRunButton = (Button) findViewById(R.id.runButton); // button to start
computation
// make sure you check whether savedInstanceState is null
if (savedInstanceState != null && savedInstanceState.containsKey(STATE_COMPUTE))
{
int value = savedInstanceState.getInt(STATE_COMPUTE);
mTask = createMyTask().execute(value); // button will be disabled in
onPreExecute()
}
}
// creation of AsyncTask moved to private method as it can now be created from 2
places
private AsyncTask<Integer, Void, BigInteger> createMyTask() {
return new AsyncTask<Integer, Void, BigInteger>() {
@Override
protected void onPreExecute() {
// button is disabled so the user can start only one computation at a
time
mRunButton.setEnabled(false);
}
@Override
protected void onCancelled() {
// button is enabled again to let the user start another computation
mRunButton.setEnabled(true);
mTask = null;
}
@Override
protected BigInteger doInBackground(Integer... params) {
return Fibonacci.recursiveFasterPrimitiveAndBigInteger(params[0]);
}
@Override
protected void onPostExecute(BigInteger result) {
mResultTextView.setText(result.toString());
// button is enabled again to let the user start another computation
mRunButton.setEnabled(true);
mTask = null;
// we add a log message to know when the computation is done
Log.i(TAG, “Computation completed in ” + MyActivity.this.toString());
Log.i(TAG, “onPostExecute () called in thread ” +
Thread.currentThread().getId());
}
};
}
public void onClick (View v) {
// we keep a reference to the AsyncTask object
mTask = createMyTask.execute(100000); // for simplicity here, we hard-code the
parameter
}
}
With this implementation, we basically tell the new instance that the previous instance was computing a certain value when it was destroyed. The new instance will then start the computation again, and the user interface will be updated accordingly用户界面也会相应的刷新.
A device does not have to be rotated to generate a change of configuration as other events are also considered a configuration change. For example, these include a change of locale or an external keyboard being connected. While a Google TV device may not be rotated (at least for now), you should still take the configuration change scenario into account when you target Google TV devices specifically as other events are still likely to occur. Besides, new events may be added in the future, which could also result in a configuration change.
NOTE: onSaveInstanceState() is not always called. It will basically be called only when Android has a good reason to call it. Refer to the Android documentation for more information.
Canceling an AsyncTask object does not necessarily mean the thread will stop
immediately though. The actual behavior depends on several things:
Whether the task has been started already
Which parameter (true or false) was passed to cancel()
Calling AsyncTask.cancel() triggers a call to onCancelled() after doInBackground()
returns, instead of a call to onPostExecute(). Because doInBackground() may still have to complete before onCancelled() is called, you may want to call
AsyncTask.isCancelled() periodically in doInBackground() to return as early as
possible. While this was not relevant in our example与本例无关, this may make your code a little bit harder to maintain since you would have to interleave交错 AsyncTask-related calls (isCancelled()) and code doing the actual work (which should ideally be AsyncTaskagnostic).
NOTE: Threads don’t always have to be interrupted when the activity is destroyed. You can use the Activity.isChangingConfiguration() and Activity.isFinishing() APIs to learn more about what is happening and plan accordingly. For example, in Listing 5�C22 we could decide to cancel the task in onStop() only when isFinishing() returns true.
In general, you should at least try to pause the background threads when your activity is paused or stopped. This prevents your application from using resources (CPU, memory, internal storage) other activities could be in dire need of(以防止其他应用占用其他Activity急需的资源).
TIP: Have a look at the source code of Shelves on http://code.google.com/p/shelves and PhotoStream on http://code.google.com/p/apps-for-android for more examples on saving a state between instantiations of activities.
Summary
Using threads can make your code more efficient and easier to maintain even on singlethreaded devices. However, multithreading can also add complexity to your application, especially when synchronization is involved and the state of the application needs to be preserved for a better user experience. Make sure you understand the ramifications分枝 of using multiple threads in your application as this can easily get out of control, and debugging can become quite difficult. Although sometimes not trivial, using multithreading can dramatically boost your application’s performance. Because multicore architectures are quickly becoming ubiquitous, adding multithreading support in your application is definitely something most of your users will benefit from.