Handler机制与生产者消费者模式

Handler机制

Handler机制在Android中通常用来更新UI。子线程执行任务,任务执行完毕后发送消息:Handler.sendMessage(),然后在UI线程Handler.handleMessage()就会调用,执行相应处理。
Handler机制有几个非常重要的类:

  • Handler:用来发送消息:sendMessage等多个方法,并实现handleMessage()方法处理回调(还可以使用Message或Handler的Callback进行回调处理,具体可以看看源码)。
  • Message:消息实体,发送的消息即为Message类型。
  • MessageQueue:消息队列,用于存储消息。发送消息时,消息入队列,然后Looper会从这个MessageQueue取出消息进行处理。
  • Looper:与线程绑定,不仅仅局限于主线程,绑定的线程用来处理消息。loop()方法是一个死循环,一直从MessageQueue里取出消息进行处理。

这几个类的作用还可以用下图解释:

Handler机制

Looper.loop()是一个死循环,为什么不会卡死主线程呢?简单的说,就是当MessageQueue为empty时,线程会挂起,一有消息,就会唤醒线程处理消息。关于这个问题可以看看这个回答:Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
Handler不仅仅可以在主线程处理消息,还可以在子线程,前提是子线程要关联Looper,标准写法为:

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };
        Looper.loop();
    }
}

一定要调用Looper.prepare()和Looper.loop()方法。Looper.prepare()使用ThreadLocal将当前线程与new出来的Looper关联,Looper.loop()开启循环处理消息。这样子,mHandler发送的消息就可以在LooperThread进行处理了。

生产者消费者模式

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这种生产消费能力不均衡的问题,所以便有了生产者和消费者模式。具体介绍可参考:聊聊并发(十)生产者消费者模式

生产者消费者模式

Handler机制与生产者消费者模式

那么Handler机制和生产者消费者模式有什么关系呢?
Handler机制就是一个生产者消费者模式。可以这么理解,Handler发送消息,它就是生产者,生产的是一个个Message。Looper可以理解为消费者,在其loop()方法中,死循环从MessageQueue取出Message进行处理。而MessageQueue就是缓冲区了,Handler产生的Message放到MessageQueue中,Looper从MessageQueue取出消息。
既然Handler机制本质上是一个生产者消费者模式,那么我们就可以脱离Android来实现一个Handler机制。

Handler

用来发送和处理消息,因此主要方法就是sendMessage()和handleMessage():

public abstract class Handler {
    private IMessageQueue messageQueue;
    public Handler(Looper looper) {
        messageQueue = looper.messageQueue;
    }
    public Handler() {
        Looper.myLooper();
    }
    public void sendMessage(Message message) {
        // 指定发送Message的Handler,方便回调
        message.target = this;
        try {
            messageQueue.enqueueMessage(message);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public abstract void handleMessage(Message msg);
}
Message

相当简单,充当一个使者的角色,重要的是要与对应的Handler绑定:target变量。

public class Message {
    private int code;
    private String msg;
    Handler target;

    public Message() { }
    public Message(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public int getCode() {
        return code;
    }
    public void setCode(int code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
}
IMessageQueue

定义的一个接口,由于MessageQueue可以有不同的实现,因此抽象一个接口出来。

public interface IMessageQueue {
    Message next() throws InterruptedException;
    void enqueueMessage(Message message) throws InterruptedException;
}
Looper

loop()方法是一个死循环,在这里处理消息,没有消息时,绑定线程会处于wait状态。使用ThreadLocal来与线程进行绑定。

public class Looper {
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    IMessageQueue messageQueue;
    private static Looper sMainLooper;
    public Looper() {
        messageQueue = new MessageQueue(2);
        // messageQueue = new MessageQueue1(2);
        // messageQueue = new MessageQueue2(2);
    }
    public static void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
    public static void prepareMainLooper() {
        prepare();
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    public static Looper getMainLooper() {
        return sMainLooper;
    }
    public static Looper myLooper() {
        return sThreadLocal.get();
    }
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        for (;;) {
            // 消费Message,如果MessageQueue为null,则等待
            Message message = null;
            try {
                message = me.messageQueue.next();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (message != null) {
                message.target.handleMessage(message);
            }
        }
    }
}
LinkedBlockingQueue实现的MessageQueue

MessageQueue是缓冲区,它需要满足以下功能:

  • 当缓冲区满时,挂起执行enqueueMessage的线程。
  • 当缓冲区空时,挂起执行next的线程。
  • 当缓冲区非空时,唤醒被挂起在next的线程。
  • 当缓冲区不满时,唤醒被挂起在enqueueMessage的线程。

所以MessageQueue最简单的实现莫过于使用LinkedBlockingQueue了。(源码|并发一枝花之BlockingQueue

public class MessageQueue implements IMessageQueue {
    private final BlockingQueue<Message> queue;
    public MessageQueue(int cap) {
        this.queue = new LinkedBlockingQueue<>(cap);
    }
    public Message next() throws InterruptedException {
        return queue.take();
    }
    public void enqueueMessage(Message message) {
        try {
            queue.put(message);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

像这样,一个简易的Handler机制就实现了。Android系统的Handler机制肯定不是那么简单,做了很多鲁棒性相关的处理,还有和底层的交互等等。另外,Android系统MessageQueue是结合Message实现的一个无界队列,意味着发送消息的队列不会阻塞。
Handler机制搭建好了,那么来测试一下吧:

public class Main {
    public static void main(String[] args) {
        MainThread mainThread = new MainThread();
        mainThread.start();
        // 确保mainLooper构建完成
        while (Looper.getMainLooper() == null) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        Handler handler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                System.out.println("execute in : " + Thread.currentThread().getName());
                switch (msg.getCode()) {
                    case 0 :
                        System.out.println("code 0 : " + msg.getMsg());
                        break;
                    case 1 :
                        System.out.println("code 1 : " + msg.getMsg());
                        break;
                    default :
                        System.out.println("other code : " + msg.getMsg());
                }
            }
        };

        Message message1 = new Message(0, "I am the first message!");
        WorkThread workThread1 = new WorkThread(handler, message1);

        Message message2 = new Message(1, "I am the second message!");
        WorkThread workThread2 = new WorkThread(handler, message2);

        Message message3 = new Message(34, "I am a message!");
        WorkThread workThread3 = new WorkThread(handler, message3);

        workThread1.start();
        workThread2.start();
        workThread3.start();
    }
    /**模拟工作线程**/
    public static class WorkThread extends Thread {
        private Handler handler;
        private Message message;

        public WorkThread(Handler handler, Message message) {
            setName("WorkThread");
            this.handler = handler;
            this.message = message;
        }

        @Override
        public void run() {
            super.run();
            // 模拟耗时操作
            Random random = new Random();
            try {
                Thread.sleep(random.nextInt(10) * 300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 任务执行完,sendMessage
            handler.sendMessage(message);
        }
    }
    /**模拟主线程*/
    public static class MainThread extends Thread {
        public MainThread() {
            setName("MainThread");
        }
        @Override
        public void run() {
            super.run();
            // 这里是不是与系统的调用一样,哈哈
            Looper.prepareMainLooper();
            System.out.println(getName() + " the looper is prepared");
            Looper.loop();
        }
    }
}

这里模拟了子线程执行任务,发送消息到主线程处理的场景。那么执行结果就是:

MainThread the looper is prepared
execute in : MainThread
other code : I am a message!

execute in : MainThread
code 0 : I am the first message!

execute in : MainThread
code 1 : I am the second message!

可以看出handleMessage的执行都是在主线程的,简易Handler机制搞定!!!

本文章到这就应该结束了,但是MessageQueue的实现方式是可以有多种的,因此来看看其不同的实现。

wait/notify实现
public class MessageQueue1 implements IMessageQueue {

    private Queue<Message> queue;
    private final AtomicInteger integer = new AtomicInteger(0);
    private volatile int count;
    private final Object BUFFER_LOCK = new Object();
    public MessageQueue1(int cap) {
        this.count = cap;
        queue = new LinkedList<>();
    }

    @Override
    public Message next() throws InterruptedException {
        synchronized (BUFFER_LOCK) {
            while (queue.size() == 0) {
                BUFFER_LOCK.wait();
            }
            Message message = queue.poll();
            BUFFER_LOCK.notifyAll();
            return message;
        }
    }

    @Override
    public void enqueueMessage(Message message) throws InterruptedException {
        synchronized (BUFFER_LOCK) {
            while (queue.size() == count) {
                BUFFER_LOCK.wait();
            }
            queue.offer(message);
            BUFFER_LOCK.notifyAll();
        }
    }
}

BUFFER_LOCK 用来处理与锁相关的逻辑。

next()方法中,如果queue的size 为0,说明现在没有消息待处理,因此执行BUFFER_LOCK.wait()挂起线程,当queue.poll();时,queue里就有了消息,需要唤醒因为没有消息而挂起的线程,所以执行BUFFER_LOCK.notifyAll();。

enqueueMessage()方法中,如果queue.size() == count说明消息满了,如果Handler继续sendMessage,queue无法继续装下,因此该线程需要挂起: BUFFER_LOCK.wait();。当执行queue.offer(message);时,queue里头保存的Message就少了一个,可以插入新的Message,因此BUFFER_LOCK.notifyAll();唤醒因为queue满了而挂起的线程。

Lock实现

既然wait/notify可以实现MessageQueue,那么ReentrantLock肯定也能实现,下面就是使用ReentrantLock实现的例子,原理上来讲是一致的。

public class MessageQueue2 implements IMessageQueue {
    private final Queue<Message> queue;
    private int cap = 0;
    private final Lock lock = new ReentrantLock();
    private final Condition BUFFER_CONDITION = lock.newCondition();
    public MessageQueue2(int cap) {
        this.cap = cap;
        queue = new LinkedList<>();
    }

    @Override
    public Message next() throws InterruptedException {
        try {
            lock.lock();
            while (queue.size() == 0) {
                BUFFER_CONDITION.await();
            }
            Message message = queue.poll();
            BUFFER_CONDITION.signalAll();
            return message;
        } finally {
            lock.unlock();
        }
    }
    @Override
    public void enqueueMessage(Message message) throws InterruptedException {
        try {
            lock.lock();
            while (queue.size() == cap) {
                BUFFER_CONDITION.await();
            }
            queue.offer(message);
            BUFFER_CONDITION.signalAll();
        } finally {
            lock.unlock();
        }
    }
}
Lock实现的优化

上面的实现过程中,入队出队是基于同一个锁的,意味着,如果有一个线程正在入队,其他线程就不能出队;有一个线程出队,其它不能入队。一个时刻只能有一个线程操作缓冲区,这显然在多线程环境下会影响性能。所以对其进行优化。
优化想法是,入队和出队互相不干扰。所以入队和出队分别需要一个锁,入队在队列的尾部进行,出队在头部进行。代码如下:

public class MessageQueue3 implements IMessageQueue {
    private final Lock putLock = new ReentrantLock();
    private final Condition notFull = putLock.newCondition();
    private final Lock takeLock = new ReentrantLock();
    private final Condition notEmpty = takeLock.newCondition();
    private Node head; // 队头
    private Node last; // 队尾
    private AtomicInteger count = new AtomicInteger(0); // 记录大小
    private int cap = 10; // 容量,默认为10

    public MessageQueue3(int cap) {
        this.cap = cap;
    }

    @Override
    public Message next() throws InterruptedException {
        Node node;
        int c = -1;
        takeLock.lock();
        try {
            while (count.get() == 0) {
                notEmpty.await();
            }
            node = head;
            head = head.next;
            c = count.getAndDecrement();
            if (c > 0) {
                notEmpty.signal();
            }
        } finally {
            takeLock.unlock();
        }
        if (count.get() < cap) {
            signalNotFull();
        }
        return node.data;
    }

    @Override
    public void enqueueMessage(Message message) throws InterruptedException {
        Node node = new Node(message);
        int c = -1;
        putLock.lock();
        try {
            while (count.get() == cap) {
                notFull.await();
            }
            // 初始状态
            if (head == null && last == null) {
                head = last = node;
            } else {
                last.next = node;
                last = last.next;
            }

            c = count.getAndIncrement();
            if (c < cap) {
                notFull.signal();
            }
        } finally {
            putLock.unlock();
        }
        if (c > 0) {
            signalNotEmpty();
        }
    }

    private void signalNotEmpty() {
        takeLock.lock();
        try {
            notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
    }

    private void signalNotFull() {
        putLock.lock();
        try {
            notFull.signal();
        } finally {
            putLock.unlock();
        }
    }
    
    static class Node {
        Message data;
        Node next;
        public Node(Message data) {
            this.data = data;
        }
    }
}

全文完~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容