探索Handler源码之等待任务执行

问题

如何在子线程中通过主线程的Handler向主线程发布任务,然后等待主线程将任务处理完成以后再继续执行子线程?

Handler#runWithScissors

Handler源码中提供了方法runWithScissors就是用来处理上面问题的,但是开发者无法调用,因为被标记为@hide了,但是实现方法可以借鉴。

 public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
        if (r == null) {
            throw new IllegalArgumentException("runnable must not be null");
        }
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout must be non-negative");
        }

        if (Looper.myLooper() == mLooper) {
            r.run();
            return true;
        }

        BlockingRunnable br = new BlockingRunnable(r);
        return br.postAndWait(this, timeout);
    }

可以看到runWithScissors的参数有两个。参数Runnable指定需要执行的任务,后面的参数是设置超时时间。如果当前线程和Handler对应的线程是同一个线程则直接执行run方法。如果线程不一致,则会执行BlockingRunnable#postAndWait方法。

 private static final class BlockingRunnable implements Runnable {
        private final Runnable mTask;
        private boolean mDone;

        public BlockingRunnable(Runnable task) {
            mTask = task;
        }

        @Override
        public void run() {
            try {
                mTask.run();
            } finally {
                synchronized (this) {
                    mDone = true;
                    notifyAll();
                }
            }
        }

        public boolean postAndWait(Handler handler, long timeout) {
            if (!handler.post(this)) {
                return false;
            }

            synchronized (this) {
                if (timeout > 0) {
                    final long expirationTime = SystemClock.uptimeMillis() + timeout;
                    while (!mDone) {
                        long delay = expirationTime - SystemClock.uptimeMillis();
                        if (delay <= 0) {
                            return false; // timeout
                        }
                        try {
                            wait(delay);
                        } catch (InterruptedException ex) {
                        }
                    }
                } else {
                    while (!mDone) {
                        try {
                            wait();
                        } catch (InterruptedException ex) {
                        }
                    }
                }
            }
            return true;
        }
    }

BlockingRunnable其实就是将发送给Handler的Runnable进行包装了一下。
postAndWait方法逻辑很简单,首先会通过Handler将BlockingRunnable发送出去,如果Handler对应的消息队列不存在或者Looper已经退出,则任务发送失败,直接返回false。否则就会对BlockingRunnable对象进行上锁,然后进入到循环(循环条件是任务是否完成),如果有超时时间,则会判断是否在指定的超时时间内任务没有结束,如果超时任务还没有结束则会直接返回false。如果没有超时,则调用wait(long timeout)方法阻塞发送任务的线程(子线程)。如果没有超时时间直接调用wait()方法阻塞发送任务的线程(子线程)。

run方法在Handler对应的线程执行的时候会调用发送任务的Runnable的run方法执行任务,执行结束以后会将用于死循环的标志位mDone置为true并调用notifyAll唤醒发送任务的线程,然后发送任务的线程(子线程)会退出循环。

wait和notify/notifyAll

JVM会为使用synchronized加锁的对象维护两个集合,Entry Set和Wait Set。
如果某个线程已经持有了对象锁,此时如果有其他线程也想获得该对象锁的话,就只能进入Entry Set,并且处于线程的BLOCKED状态。当对象锁被释放的时候,JVM会唤醒处于Entry Set中的某个线程,这个线程的状态会从BLOCKED转变为RUNNABLE。
如果线程调用了wait()方法,那么线程会释放该对象的锁,进入到Wait Set,并且处于线程的WAITING状态。当对象的notify()方法被调用时,JVM会唤醒处于Wait Set中的某个线程,这个线程的状态就从WAITING转变为RUNNABLE;或者当notifyAll()方法被调用时,Wait Set中的全部线程会转变为RUNNABLE状态。所有Wait Set中被唤醒的线程会被转移到Entry Set中。

每当对象的锁被释放后,那些所有处于RUNNABLE状态的线程会共同去竞争获取对象的锁,最终会有一个线程真正获取到对象的锁,而其他竞争失败的线程继续在Entry Set中等待下一次竞争机会。

为什么wait方法要放到循环中?
因为调用wait()的线程永远不能确定其他线程会在什么状态下notify(),所以必须在被唤醒、抢占到锁并且从wait()方法退出的时候再次进行特定条件的判断,如果满足条件则继续往下执行,如果不满足条件则继续等待。

弊端

  • 超时无法取消

从上面的源码可以看出来,超时以后,直接返回false退出了,但是对应的任务还在Handler的消息队列中,没有被移除掉,最终任务还是会被执行。

  • 死锁

如果通过Handler发送任务以后,通过Looper.quit退出消息循环,会清理未执行的任务,此时通过Handler发送任务的线程永远得不到唤醒,如果线程还持有其他锁,还会造成死锁。所以为了安全起见,发送任务以后,不允许Handler对应的Looper调用quit退出,或者使用quitSafely方法安全退出,因为quit会清理消息队列中所有消息,而quitSafely只会清理当前时间点之后的消息,之前的消息会保留,任务依然可以得到执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值