Handlers and Loopers
Android在android.os包中定义了两个类,作为在多线程应用中线程通信的基石:
(1) Handler
(2) Looper
当创建一个AsynTask对象,隐藏了Handler和Looper的细节,某些情况下需要显式的使用handlers和loopers,比如,当你需要去post一个Runnable对象到其他线程而不是主线程。
Handlers
Listing 5-2给出了Handler和Looper如何一起工作的概述:使用Handler对象post一个Runnable对象到Looper的message队列。应用的主线程已经有了一个message queue,所以不需要显式创建。然而,你自己创建的线程没有自动创建message queue和message loop,所以有需要的话得自己创建。Listing 5-7给出了如何创建一个带有Looper的线程。Listing 5-7 带有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(); //绑定一个looper到这个线程
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
// 在这里处理message
}
}
};
// handler被绑定到线程的looper
Looper.loop(); // 不要忘记调用loop()开始message loop
// loop()不会返回,直到停止(比如,当调用Looper.quit())
}
}
NOTE: handler对象在run()方法里面创建,它需要绑定到一个指定的looper(),同样当Looper.prepare()被调用的时候在run()中创建。结果,在线程被孵化前调用getHandler()将会返回null。
一旦线程运行起来,你可以post Runnable对象或者发送消息到message queue,如Listing 5-8所示。
Listing 5-8 Posting Runnables and Sending Messages
MyThread thread = new MyThread("looper thread");
thread.start();
// later...
Handler handler = thread.getHandler();
// 注意:如果handler没有初始化,这里将会返回null
// 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; // 定义你自己的值
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:使用Message.obtain()或者Handler.obtainMessage()接口去获取一个Message对象,他们将从global message pool返回一个Message对象,比每次分配一个新的实例更加有效。这些API同样使得设置message的不同字段更加的简单。
Loopers
Android提供了一个更加简单的方式使用looper线程,HandlerThread,也使得避免Listing 5-8提到的潜在条件竞争更简单,即getHandler()可能依然返回null,即使线程已经开始。Listing 5-9给出了如何使用HandlerThread类。
Listing 5-9 使用HandlerThread类
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(); // 会block,直到线程的Looper对象初始化完成
mHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
// 在这里处理message
}
}
};
}
}
既然handler在start()中创建而不是在run()方法中,在start()方法返回后他将会是可用的,在start()方法返回后调用getHandler()不会有任何竞争条件。
Data Types
我们已经看了孵化线程的两种方式,使用Thread和AsyncTask类。当2个或者更多的线程访问同一份数据,你需要保证数据类型支持同时访问。Java语言为了这个目的在java.util.concurrent包中定义了许多类:
(1) ArrayBlockingQueue
(2) ConcurrentHashMap
(3) ConcurrentLinkedQueue
(4) ConcurrentSkipListMap
(5) ConcurrentSkipListSet
(6) CopyOnWriteArrayList
(7) CopyOnWriteArraySet
(8) DelayQueue
(9) LinkedBlockingDeque
(10) LinkedBlockingQueue
(11) PriorityBlockingQueue
(12) SynchronousQueue
你需要基于应用的需要认真的选择数据类型。同样,他们的同步实现不是暗示实现必须是原子的。实际上,许多操作不是原子的,设计上就不是。比如,在ConcurrentSkipListMap类putAll()方法不是原子的。一个同步的实现仅仅意味着数据结构在多线程访问的时候不会被打断。
Synchronized,Volatile, Memory Model
如果你要在多个线程之间共享对象,但是没有实现任何锁机制,你可以使用synchronized关键字去保证你的访问是线程安全的,如Listing 5-10所示。
Listing 5-10 使用Synchronized关键字
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;
}
}
Listing 5-7给出的add和multiplyAndAdd方法是同步方法,这意味着:
(1) 如果一个线程执行一个同步方法,另外的线程尝试去调用同一对象任何同步方法需要等待第一个线程结束
(2) 当一个同步方法存在,对象的updated状态对其他的所有线程是可见的
第一项是很直观的。第二个仍然需要解释一下。作为事实,Java内存模型中,变量在一个线程中的修改不会立即在其他线程可见。实际上,可能会根本不可见。考虑Listing 5-11中的代码:如果一个线程调用MyClass.loop(),将来的某个点另外一个线程调用MyClass.setValue(100),第一个线程可能还没有结束;简单的因为Java语言的内存模型,可能会继续打印一个不是100的值。
Listing 5-11 Java内存模型
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) {
// 忽略
}
}
}
}
你有两个选择去修正它:
(1) 使用synchronized关键字,如Listing 5-12
(2) 使用volatile关键字,如Listing 5-13所示
Listing 5-12 添加Synchronized关键字
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) {
// 忽略
}
}
}
}
Listing 5-13 添加Volatile关键字
public class MyClass {
private static final String TAG = "MyClass";
private static volatile int mValue = 0; // 我们添加了volatile关键字,移除synchronize关键字
public static void setValue(int n) {
mValue = n; // 如果语句mValue=n不是原子操作,你仍然需要使用synchronized关键字
}
public static void loop() {
while (mValue != 100) {
Log.i(TAG, "Value is " + mValue);
Thread.sleep(1000);
} catch (Exception e) {
// 忽略
}
}
}
NOTE:保证你理解哪个语句块是原子性的。比如,value++不是原子性的,而value = 1是。很重要的是,如果语句的原子操作,volatile关键字可以修正同步问题。如果不是,你需要去使用synchronized关键字。
你可以通过synchronized语句提升并发和吞吐量,如Listing 5-14所示,反对使整个方法同步。在这些例子中,期望去仅仅保护需要保护的一部分(即mValue被修改的地方),但是把log信息放在同步语句块外面。你可以使用对象作为lock。
Listing 5-14 Synchronized语句
public class MyOtherClass {
private static final String TAG = "MyOtherClass";
private int mValue;
private Object myLock = new Object(); //
public MyClass(int n) {
mValue = n;
}
public void add (int a) {
synchronized (myLock) {
mValue += a;
}
Log.i(TAG, "add: a=" + a); // 不需要block
}
public void multiplyAndAdd (int m, int a) {
synchronized (myLock) {
mValue = mValue * m + a;
}
Log.i(TAG, " multiplyAndAdd: m= " + m + ", a=" + a); // 不需要block
}
}
使方法或者语句块synchronized是保证你的类支持并发访问的最简单的方式。然而,当不是所有的东西都需要保护的时候可能会减少吞吐量,更坏的情况是,可能会导致死锁。实际上,当你在一个synchronized块中调用另外一个对象的方法可能会导致死锁,可能会尝试去获取一个已经locked对象锁并等待你自己对象的锁。
TIP:不要在一个同步语句块中调用另外一个对象的方法,除非你可以保证不会发生死锁。通常,仅当你是另外对象类代码的作者你可以保证。
事实上,如果你疑虑它是否会工作的话,最好避免在不同的线程上访问同一个对象。Java定义的类是引用,Android代码是可用的,你可以参考这些类的实现去理解可以利用的不同的技术。另外,为了简化你的开发,Java定义了许多类,已经支持线程安全或者并发开发,可以用来作为你自己的算法基础。