Handler机制与生产者消费者模式
本文梳理了 Handler 的源码,并详细阐述了 Handler 与生产者消费者模式的关系,最后给出了多版自定义 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
里取出消息进行处理。
这几个类的作用还可以用下图解释:
Looper.loop()
是一个死循环,为什么不会卡死主线程呢?简单的说,就是当MessageQueue
为空时,线程会挂起,一有消息,就会唤醒线程处理消息。关于这个问题可以看看这个回答:Android中为什么主线程不会因为Looper.loop()里的死循环卡死? 。
Handler不仅仅可以在主线程处理消息,还可以在子线程,前提是子线程要关联Looper,标准写法为:
class LooperThread extends Thread {
public void run() {
Looper.prepare();
Looper.loop();
}
}
一定要调用Looper.prepare()
和Looper.loop()
方法。Looper.prepare()
使用ThreadLocal
将当前线程与new出来的Looper
关联,Looper.loop()
开启循环处理消息。这样子,mHandler发送的消息就可以在LooperThread
进行处理了。系统中最典型的实例是:android.os.HandlerThread
。
二、生产者消费者模式
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这种生产消费能力不均衡的问题,所以便有了生产者和消费者模式。具体介绍可参考:聊聊并发(十)生产者消费者模式
三、Handler 与生产者消费者模式
那么Handler机制和生产者消费者模式有什么关系呢?
Handler机制就是一个生产者消费者模式。可以这么理解,Handler
发送消息,它就是生产者,生产的是一个个Message
。Looper
可以理解为消费者,在其loop()
方法中,死循环从MessageQueue
取出Message
进行处理。而MessageQueue
就是缓冲区了,Handler产生的Message
放到MessageQueue
中,Looper
从MessageQueue
取出消息。
既然 Handler 机制本质上是一个生产者消费者模式,那么我们就可以脱离 Android 来实现一个 Handler 机制。
1.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);
}
2.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;
}
}
3.IMessageQueue
定义的一个接口,由于MessageQueue
可以有不同的实现,因此抽象一个接口出来。
public interface IMessageQueue {
Message next() throws InterruptedException;
void enqueueMessage(Message message) throws InterruptedException;
}
4.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);
}
}
}
}
5.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的实现方式是可以有多种的,因此来看看其不同的实现。
6.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满了而挂起的线程。
7.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();
}
}
}
8.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;
}
}
}
Handler机制与生产者消费者模式的更多相关文章
- Java 生产者消费者模式详细分析
*/ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #333; background: #f8f8f8; } .hl ...
- python3全栈开发-多进程的守护进程、进程同步、生产者消费者模式(重点)
一.守护进程 主进程创建守护进程 其一:守护进程会在主进程代码执行结束后就终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes a ...
- java ReentrantLock结合条件队列 实现生产者-消费者模式 以及ReentratLock和Synchronized对比
package reentrantlock; import java.util.ArrayList; public class ProviderAndConsumerTest { static Pro ...
- java 多线程 22 :生产者/消费者模式 进阶 利用await()/signal()实现
java多线程15 :wait()和notify() 的生产者/消费者模式 在这一章已经实现了 wait/notify 生产消费模型 利用await()/signal()实现生产者和消费者模型 一样 ...
- Celery 框架学习笔记(生产者消费者模式)
生产者消费者模式 在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类.函数.线程.进程等).产生数据的模块,就形象地称为生产 ...
- Java并发编程()阻塞队列和生产者-消费者模式
阻塞队列提供了可阻塞的put和take方法,以及支持定时的offer和poll方法.如果队列已经满了,那么put方法将阻塞直到有空间可用:如果队列为空,那么take方法将会阻塞直到有元素可用.队列可以 ...
- day 28 :进程相关,进程池,锁,队列,生产者消费者模式
---恢复内容开始--- 前情提要: 一:进程Process 1:模块介绍 from multiprocessing import Process from multiprocessing impo ...
- 基于Java 生产者消费者模式(详细分析)
Java 生产者消费者模式详细分析 本文目录:1.等待.唤醒机制的原理2.Lock和Condition3.单生产者单消费者模式4.使用Lock和Condition实现单生产单消费模式5.多生产多消费模 ...
- 反应器模式 vs 生产者消费者模式
相似点: 从结构上,反应器模式有点类似生产者消费者模式,即有一个或多个生产者将事件放入一个Queue中,而一个或多个消费者主动的从这个Queue中Poll事件来处理: 不同点: Reactor模式则并 ...
随机推荐
- Solution -「校内题」矩阵求和
Description 共 \(T\) 组数据.对于每组数据,给定 \(a, b, n\),求 \(\sum_{i = 1}^{n} \sum_{j = 1}^{n} \gcd(a^i - b^i, ...
- AtCoder Beginner Contest 247 F - Cards // dp + 并查集
原题链接:F - Cards (atcoder.jp) 题意: 给定N张牌,每张牌正反面各有一个数,所有牌的正面.反面分别构成大小为N的排列P,Q. 求有多少种摆放方式,使得N张牌朝上的数字构成一个1 ...
- 「游戏引擎 浅入浅出」4.1 Unity Shader和OpenGL Shader
「游戏引擎 浅入浅出」从零编写游戏引擎教程,是一本开源电子书,PDF/随书代码/资源下载: https://github.com/ThisisGame/cpp-game-engine-book 4.1 ...
- 字节输出流的续写和换行和字节输入流InputStream类&FileInputStream类介绍
数据追加续写 每次程序运行,创建输出流对象,都会清空目标文件中的数据.如何保目标文件中的数据,还能继续添加新数据呢? public FileOutputStream(File file,boolean ...
- Luogu1993 小K的农场 (差分约束)
\(if \ a - b <= c, AddEdge(b, a, c)\) Be careful, MLE is not good. #include <cstdio> #inclu ...
- Git 03 理论
参考源 https://www.bilibili.com/video/BV1FE411P7B3?spm_id_from=333.999.0.0 版本 本文章基于 Git 2.35.1.2 四个区域 G ...
- error setting certificate verify locations
描述 在使用 git clone 克隆 GitHub 或者 Gitee 上的项目时,报如下错误: error setting certificate verify locations: CAfile: ...
- "蔚来杯"2022牛客暑期多校训练营9 G Magic Spells【马拉车+哈希】
四川今天又上热搜了,继南部疫情的未雨绸缪后,龙槽沟是真的倾盆大雨了.我没有兴趣虚伪矫情地对罹难的游人表达同情,因为人与人互不相通徒增谈资:我也没有兴趣居高临下地对擅闯的愚人表达不屑,因为你我皆为乌合之 ...
- 基于bert_bilstm_crf的命名实体
前言 本文将介绍基于pytorch的bert_bilstm_crf进行命名实体识别,涵盖多个数据集.命名实体识别指的是从文本中提取出想要的实体,本文使用的标注方式是BIOES,例如,对于文本虞兔良先生 ...
- 给网站添加pjax无刷新,换页音乐不中断
自从博客加了悬浮音乐播放器后就一直在折腾换页音乐不中断的功能 在网上查找后发现想要实现换页音乐不中断的功能必须要为博客加pjax,于是又苦苦寻找并尝试了一番 最后发现网上实现pjax功能基本上是两种方 ...