Android中异步转同步(主线程等待子线程)方法总结

前言

Android日常开发中,平时获取子线程返回结果以异步回调的方式获取返回到主线程,其实也可以通过某种方法转为同步返回,不过要慎用在UI主线程中使用,以防ANR。
应用场景:
1. 单个线程处理结果返回到主线程
2. 多个子线程并发请求,最终合并返回结果到主线程
下面介绍8种方法实现主线程等待子线程
介绍之前先贴出测试类NetRequest

/**
     * 模拟网络请求类
     */
    private static class NetRequest implements Runnable, Callable<String> {
        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<String> task = exec.submit((Callable<String>) 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包下找到相关类研究或者网上研究。

猜你喜欢

转载自blog.csdn.net/u011082160/article/details/99441697