美文网首页
Android-HandlerThread中wait/notif

Android-HandlerThread中wait/notif

作者: 九号锅炉 | 来源:发表于2019-04-19 10:54 被阅读0次

最近在阅读HanlerThread源码时在getLooper中有wait函数,在run中用到了notifyall函数。于是就思考这两个函数的作用是什么?为什么需要在这里添加这两个函数?经过一番研究终于理解了,这边做一个总结。

    /**
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason isAlive() returns false, this method will return null. If this thread
     * has been started, this method will block until the looper has been initialized.  
     * @return The looper.
     */
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

从消费者生产者模式说起

为了理解wait/notify/notifyall的作用和关系,首先通过消费者生产者设计模式为例来介绍。什么是生产者消费者?简单来说就是为了保持供需平衡的一种手段。生产者在发现仓库里面没有货物了再去生产,当生产到一定数量之后就停止生产,并告诉消费者有货了你可以过来消费了;而消费者会不断从仓库中消费货物,如果没有了就等待,并告诉生产者快去生产货物。这样做的好处就是货物不会堆积,保证供需平衡。经典的生产者消费者模式代码如下所示:

// 生产者
public class Producer implements Runnable {
    List<Integer> products;
    public Producer(List<Integer> products) {
        this.products = products;
    }
    @Override
    public void run() {
        while (true) {
            produce();
        }
    }
    private void produce() {
        synchronized (products) {
            try {
                // 当仓库产品数量到达1个时停止生产。
                // 调用wait()当前线程释放锁,并进入等待池队列
                while (products.size() == 1) {
                    products.wait();
                }
                Thread.sleep(1000);
                cache.add(1);
                // 通知消费者消费产品。
                cache.notify();
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
// 消费者
public class Consumer implements Runnable {
    List<Integer> products;
    public Consumer(List<Integer> products) {
        this.products = products;
    }
    @Override
    public void run() {
        while (true) {
            consume();
        }
    }
    private void consume() {
        synchronized (products) {
            try {
                // 当仓库为空时,停止消费。
                // 调用wait()当前线程释放锁,并进入等待池队列
                while (products.isEmpty()) {
                    products.wait();
                }
                cache.remove(0);
                // 通知生产者生产产品
                cache.notify();
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

在结合生产者消费者模式的代码分析之前先介绍一下等待池和锁池。
等待池:当一个线程调用某对象的wait方法,该线程就会释放该对象的锁,并进入该对象的等待池队列。
锁池:当一个线程已经拥有一个对象的锁时,其他线程想要进入该对象的synchronized的方法,就必须先获得这个对象的锁,但是该对象的锁已经被线程拥有,因此其他对象就会进入该对象的锁池。
生产者和消费者各自开启一个线程进行处理,当调用wait时,其实是将当前线程的锁释放,并进入该锁的等待池队列。而调用notify可以将该对象的等待池队列中随机一个线程唤醒进入锁池,去竞争该对象的锁。而调用notifyall可以将该对象的等待池队列中所有线程都移入该对象的锁池,去竞争该对象的锁。这里注意到一种可能引起死锁的情况:
假如有一个生产者P1和两个消费者C1 C2
1 消费者C1 C2发现仓库为空,都进入等待池。
2 P1 生产一个产品后,也进入等待池,并唤醒等待池中的C2,C2进入锁池去竞争该对象的锁。
3 C2 竞争到该锁,并消费了产品再次进入等待池,并随机唤醒一个等待池的线程,假如唤醒的是C1。
4 C1检测到当前仓库为空,直接进入等待池。这时候P1 C1 C2都在等待池中,没有人来唤醒他们,就造成了死锁。
假如用notifyAll就不会出现这种情况。因为如果使用notifyall,在第三步中C2会把C1和P1一起唤醒,即便C1检测到当前仓库为空,P1也会开始生产产品,不会造成死锁。

HandlerThread中wait和notifyall的使用

理解了wait/notify/notifyall的作用之后,再回过头来看HandlerThread中为什么在run函数中需要用到notifyall。
首先我们来看getHandlerLoop函数。首先判断这个looper所在线程是否启动,如果启动则进入同步块。在同步块中判断如果looper还没有创建成功,就将该线程移入等待池.直到run中得到looper之后调用notifyall唤醒该线程,从而得到looper.因此这里使用wait/notifyall来解决同步问题,保证只有线程创建成功并且looper创建成功才能得到looper.

相关文章

网友评论

      本文标题:Android-HandlerThread中wait/notif

      本文链接:https://www.haomeiwen.com/subject/dxzfgqtx.html