Android日常开发中,平时获取子线程返回结果以异步回调的方式获取返回到主线程,其实也可以通过某种方法转为同步返回,不过要慎用在UI主线程中使用,以防ANR。
应用场景:
1. 单个线程处理结果返回到主线程
2. 多个子线程并发请求,最终合并返回结果到主线程
下面介绍8种方法实现主线程等待子线程:
介绍之前先贴出测试类NetRequest
:
/**
* 模拟网络请求类
*/
private static class NetRequest implements Runnable, Callable {
private String msg;
private int way;
public NetRequest() {
way = -1;
}
private CountDownLatch countDownLatch;
public NetRequest(int way, CountDownLatch countDownLatch) {
this.way = way;
this.countDownLatch = countDownLatch;
}
private CyclicBarrier cyclicBarrier;
public NetRequest(int way, CyclicBarrier cyclicBarrier) {
this.way = way;
this.cyclicBarrier = cyclicBarrier;
}
private Semaphore semaphore;
public NetRequest(int way, Semaphore semaphore) {
this.way = way;
this.semaphore = semaphore;
}
private Phaser phaser;
public NetRequest(int way, Phaser phaser) {
this.way = way;
this.phaser = phaser;
}
@Override
public void run() {
switch (way) {
case 1:
msg = createData();
if (countDownLatch != null) {
countDownLatch.countDown();//计数减1
}
break;
case 2:
msg = createData();
if (cyclicBarrier != null) {
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
break;
case 3:
if (semaphore != null){
msg = createData();
semaphore.release();
}
break;
case 4:
if (phaser != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
msg = createData();
phaser.arriveAndDeregister();
}
break;
default:
msg = createData();
break;
}
}
private String createData() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
msg = "hello world:" + System.currentTimeMillis();
return msg;
}
public String getMsg() {
return msg;
}
@Override
public String call() throws Exception {
return createData();
}
}
特别注意:以下代码在主线程执行操作,即可验证结果
1.使用轮询
NetRequest netRequest = new NetRequest();
Thread thread = new Thread(netRequest);
thread.start();
while (true) {
String msg = netRequest.getMsg();
if (!TextUtils.isEmpty(msg)) {
textView.setText(netRequest.getMsg());//输出: hello world和时间戳
break;
}
}
缺点:如果子线程因某种原因永远执行不完,导致永远拿到不返回结果,容易ANR.
2.使用sleep
直接在主线程睡眠,等待子线程返回
NetRequest netRequest = new NetRequest();
Thread thread = new Thread(netRequest);
thread.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
textView.setText(netRequest.getMsg());
缺点:不明确子线程执行时间情况下,不一定能拿得到返回结果
3.使用join
阻塞调用此方法的线程进入 TIMED_WAITING 状态,直到线程完成。通常用于在main()主线程内,等待其它线程完成再结束main()主线程。
NetRequest netRequest = new NetRequest();
Thread thread = new Thread(netRequest);
thread.start();
try {
thread.join();
String msg = netRequest.getMsg();
textView.setText(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
4.使用Future‘
NetRequest netRequest = new NetRequest();
ExecutorService exec = Executors.newFixedThreadPool(1);
Future task = exec.submit((Callable) netRequest);
try {
String s = task.get();//阻塞等待
textView.setText(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}’
从上面可知,这是线程创建的又一种实现方式,只不过是阻塞异步的
5.使用CountDownLatch
CountDownLatch是一种java.util.concurrent包下一个同步工具类,它允许一个或多个线程等待直到在其他线程中一组操作执行完成。CountDownLatch 是计数器, 线程完成一个就记一个, 就像报数一样, 只不过是递减的.
CountDownLatch countDownLatch = new CountDownLatch(1);
NetRequest netRequest = new NetRequest(1, countDownLatch);
Thread thread = new Thread(netRequest);
thread.start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
String msg = netRequest.getMsg();
textView.setText(msg);
6.使用同步屏障CyclicBarrier
JDK1.5开始提供的并发编程辅助工具类,也可以用于多线程。线程间同步阻塞是使用的是ReentrantLock,可重入锁。
CountDownLatch和CyclicBarrier区别:
CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。 CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待,类似有福同享,有难同当
CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> Log.i("SIMPLE_LOGGER", "开始执行"));
NetRequest netRequest = new NetRequest(2, cyclicBarrier);
Thread thread = new Thread(netRequest);
thread.start();
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
String msg = netRequest.getMsg();
textView.setText(msg);
7.使用信号量Semaphore
这个也是Java并发包中另外一个重量级的类Semaphore,这个类从字面意义上理解是"信号量"。平时自己用得不多。举个例子,假如10个人比赛100米冲刺,有5条赛道,被安排到的5个人才能在跑道上比赛,其余等待。
Semaphore与CountDownLatch相似,不同的地方在于Semaphore的值被获取到后是可以释放的,并不像CountDownLatch那样一直减到底。它也被更多地用来限制流量,类似阀门的 功能。如果限定某些资源最多有N个线程可以访问,那么超过N个主不允许再有线程来访问,同时当现有线程结束后,就会释放,然后允许新的线程进来。
Semaphore semaphore = new Semaphore(0);//默认为非公平信号量,当前资源数量大于0,表示信号量处于触发,等于0表示资源已经耗尽故信号量处于末触发
NetRequest netRequest = new NetRequest(3, semaphore);
Thread thread = new Thread(netRequest);
thread.start();
try {
semaphore.acquire();
String msg = netRequest.getMsg();
textView.setText(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
8.使用移相器Phaser
也是一个重量级并发类,jdk7被引入,用来解决控制多个线程分阶段共同完成任务的情景问题。其作用相比CountDownLatch和CyclicBarrier更加灵活。
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
Phaser phaser = new Phaser();
phaser.register();
NetRequest netRequest = new NetRequest(4, phaser);
Thread thread = new Thread(netRequest);
thread.start();
phaser.register();
phaser.arriveAndAwaitAdvance();
String msg = netRequest.getMsg();
textView.setText(msg);
}
除了上面几种方法,估计还有其它实现方法,有待研究。这次只是简单介绍用法,如果想更深入了解上面类的用法及其原理,可以到java.util.concurrent包下找到相关类研究或者网上研究。