对于在Android开发中进行多线程编程来说,线程同步是一个需要经常面对的问题。例如主线程创建了几个子线程来执行复杂的计算,要求所有的子线程执行完后返回结果给主线程,主线程才能继续后续的操作,此时就需要考虑线程同步了。我也是从阅读Android源码中关于相机的部分后才发现有两种方式可以比较容易的实现线程同步,下面一一讲解。
1.采用Thread.join()函数
在当前线程中调用thread.join()会导致当前线程阻塞,此时只有当thread线程执行完后,当前线程才能继续往下执行。
package com.android.test;
import java.util.concurrent.CountDownLatch;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class ThreadJoinTest extends Activity {
CountDownLatch mCountDownLatch = new CountDownLatch(1);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
for(int i = 0; i < 5; i++){
Log.i("com", "thread i = " + i);
Thread.sleep(500);
}
} catch (Exception e) {
// TODO: handle exception
}
}
});
thread.start();
Log.i("com", "main start");
try {
thread.join();
//thread.join(1500);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
Log.i("com", "main end ");
}
}
程序运行后log如下:
06-22 21:40:07.953: INFO/com(17958): main start
06-22 21:40:07.953: INFO/com(17958): thread i = 0
06-22 21:40:08.453: INFO/com(17958): thread i = 1
06-22 21:40:08.953: INFO/com(17958): thread i = 2
06-22 21:40:09.453: INFO/com(17958): thread i = 3
06-22 21:40:09.953: INFO/com(17958): thread i = 4
06-22 21:40:10.453: INFO/com(17958): main end
从log可以看出,程序执行后,首先主线程打印了"main start",执行到thread.join()后,被阻塞了,此时子线程开始打印log,而且时间间隔都是500毫秒,当子线程执行完后,主线程才最终打印出"main end"。
有些情况下子线程因为某种原因也可能被阻塞,则此时主线程可能永远得不到继续执行,所以我们可以采用thread.join()的一个重载方法join(long millis),其可以传入一个时间参数,表示等到thread线程执行完或者超过mills时间后才能允许调用这个函数的线程继续往下执行,例如把刚才代码中的join()换成thread.join(1500),则程序运行后log如下:
06-22 22:12:17.183: INFO/com(19691): main start
06-22 22:12:17.183: INFO/com(19691): thread i = 0
06-22 22:12:17.683: INFO/com(19691): thread i = 1
06-22 22:12:18.183: INFO/com(19691): thread i = 2
06-22 22:12:18.683: INFO/com(19691): main end
06-22 22:12:18.683: INFO/com(19691): thread i = 3
06-22 22:12:19.183: INFO/com(19691): thread i = 4
由于子线程执行完的时间超过1500毫秒,所以在thread.join(1500)执行完并等待了1500毫秒后,主线程开始再次执行,注意此时子线程并没有停止,而是跟主线程同时执行。 2.采用CountDownLatch
CountDownLatch内部维护一个计数器,初始化的时候会传入一个整型值作为计数器的初始值,其主要包括两个函数,await()和countDown(),当在某个线程中调用了await(),则该线程被阻塞,每调用countDown()则计数器的值减1,只有当计数器为0时刚才被阻塞的线程才能继续执行,这里修改官方上的demo来说明情况。
package com.android.test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class CountDownLatchTest extends Activity {
private final int N = 5;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(N);
for (int i = 0; i < N; ++i){ // create and start threads
new Thread(new Worker(startSignal, doneSignal)).start();
}
Log.i("com", "main thread start");
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Log.i("com", "main thread doing");
startSignal.countDown(); // let all threads proceed
try {
//doneSignal.await(2,TimeUnit.SECONDS);
doneSignal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("com", "main thread finish");
}
class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
public void run() {
try {
Log.i("com", "Sub Thread start");
//startSignal.await(2,TimeUnit.SECONDS);
startSignal.await();
doWork();
doneSignal.countDown();
} catch (InterruptedException ex) {} // return;
}
void doWork() {
Log.i("com", "Sub Thread doing");
try {
Thread.sleep(2000);
Log.i("com", "Sub Thread end");
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
程序打印log如下:
06-23 09:04:46.863: INFO/com(28948): Sub Thread start
06-23 09:04:46.863: INFO/com(28948): Sub Thread start
06-23 09:04:46.863: INFO/com(28948): Sub Thread start
06-23 09:04:46.863: INFO/com(28948): Sub Thread start
06-23 09:04:46.863: INFO/com(28948): main thread start
06-23 09:04:46.863: INFO/com(28948): Sub Thread start
06-23 09:04:48.863: INFO/com(28948): main thread doing
06-23 09:04:48.863: INFO/com(28948): Sub Thread doing
06-23 09:04:48.863: INFO/com(28948): Sub Thread doing
06-23 09:04:48.863: INFO/com(28948): Sub Thread doing
06-23 09:04:48.863: INFO/com(28948): Sub Thread doing
06-23 09:04:48.863: INFO/com(28948): Sub Thread doing
06-23 09:04:50.863: INFO/com(28948): Sub Thread end
06-23 09:04:50.863: INFO/com(28948): Sub Thread end
06-23 09:04:50.863: INFO/com(28948): Sub Thread end
06-23 09:04:50.863: INFO/com(28948): Sub Thread end
06-23 09:04:50.863: INFO/com(28948): Sub Thread end
06-23 09:04:50.863: INFO/com(28948): main thread finish
在代码中,尽管所有子线程在创建后立马就启动了,并且主线程调用了Thread.sleep(2000)阻塞自己2秒,可是子线程的log还是在main thread doing之后才开始打印,这正是由于在每个子线程内部都调用了startSignal.await(),所以需要等待主线程调用完startSignal.countDown()才能继续后面的操作。之后主线程中也调用了doneSignal.await(),而doneSignal所设置的计数器为5,所以这时又得等到所有子线程都执行完doneSignal.countDown()后才能打印出main thread finish。这样就达到了主线程和子线程的相互同步。当然await()也有另一个重载函数await (long timeout, TimeUnit unit),其timeout类似join(),不过要比join好的一点是它可以传入一个时间单位TimeUnit,设置时间更灵活点,具体可以参见sdk。