首先看下android底层Handler消息机制的大致流程:
1、发送消息:Handler——》sendMessage——》MessageQueue——》enqueueMessage (将消息添加到队列)
2、处理消息:Loop——》MessageQueue——》next() (使用消息)——》dispatchMessage——》handleMessage
还需要知道:Handler中使用了生产者-消费者模式
生产者:往队列添加消息,当队列满的时候,不能再添加消息了,此时block(即消息阻塞了),当生产一个消息的时候,通知消费者有消息可以消费了。
消费者:从队列消费消息(获取),如果队列为空,则block,当消费者消费了一个消息的时候,通知生产者可以再生产消息。
下面开始一步一步结合android源码进行分析:
1、发送消息流程分析:
首先平时创建Handler实例对象时基本都是继承Handler或者直接new Handler(),因此先看无参构造函数Handler()。
源码:
/**
* Default constructor associates this handler with the {@link Looper} for the
* current thread.
*
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*/
public Handler() {
this(null, false);
}
然后进入this方法:
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
到这里知道了handler实例构造过程,这里面有四个参数mLooper、mQueue、mCallback、mAsynchronous可以先不管,
得到handler对象后再看sendMessage()方法:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
再看sendMessageDelayed方法:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
接着sendMessageAtTime方法,MessageQueue queue = mQueue;语句用到了刚构造参数中的mQueue变量:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
再进入enqueueMessage,这里就看到了其实sendMessage方法最终调用了MessageQueue的enqueueMessage方法,并且注意这句msg.target = this的赋值,后面会看到分析这个参数的作用非常重要的:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
到这里会注意到这四个参数mLooper、mQueue、mCallback、mAsynchronous中,我们只需要关注前两个参数,后面两个这里就不分析了,本文只分析Handler消息机制的基本流程并简单的实现其消息机制框架来帮助小伙伴们加深理解。
到这里就有两个地方需要了解了:
(1)、mLooper = Looper.myLooper()做了什么,大致猜下肯定是返回了Looper消息循环的实例对象;
(2)、MessageQueue的enqueueMessage方法做了什么处理。
好了,先处理第一个问题,看源码:
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
a、ThreadLocal类用来提供线程内部的局部变量。
这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量
提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程
每个线程维护一个 ThreadLocalMap 的映射表,
映射表的 key 是 ThreadLocal 实例本身,value 是要存储的副本变量。
ThreadLocal 实例本身并不存储值,它只是提供一个在当前线程中找到副本值的 key。
value是真正需要存储的Object。
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static void main(String[] args) {
// 去除多余的代码,只看关键地方.......
Looper.prepareMainLooper();
// 多余的代码......
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
现在就明白了吧,原来是App启动的时候就会在App内部创建一个主消息循环来进行消息处理,并且无限循环来处理消息。
// 首先如果从这段代码中可以看出:你可以将消息队列看成是一个根据时间的先后顺序来排序的队列。
// 这点非常重要才有助于理解,由于本文不打算实现这个这个延迟功能,因此只会简单的分析涉及到的延迟操作逻辑
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// ...
// 这里使用synchronized关键字来保证线程安全
synchronized (this) {
// ...
// 关键点【1】 when其实就是需要延迟发送消息的时间
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 这里的意思是该消息循环中第一个消息(即p==null时)或者不需要延迟的消息(when==0)
// 或者当前加入队列的消息msg比当前排在最前面的那个待处理的消息的延迟时间还要短(when<p.when)
// New head, wake up the event queue if blocked.
msg.next = p;
// mMessages其实就是当前最前面的第一个需要处理消息
mMessages = msg;
// mBlocked参数的意思就是消息阻塞,注意:改值赋值为true时是在消费者那里消费时队列为空时或者等待轮询一次的时候才赋值为空(即next()方法处)
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) { //
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 关键点【2】当消息阻塞的时候,需要唤醒消息循环,其实就是生产者发现消息循环被阻塞了肯定要唤醒啦
nativeWake(mPtr);
}
}
return true;
}
到这里我们就基本明白了sendMessage其实就是将消息添加到消息队列中并且按时间先后排序,并且根据消息是否阻塞决定是否唤醒消息循环:阻塞了就需要唤醒。
// 消息循环开始工作
public static void loop() {
// 这里myLooper()之前讲过就是获取了当前线程中使用的唯一的Looper,
// 面试可能会问:Looper是运行在哪个线程中的呢?其实每个线程都对应一个Looper即消息循环,因此你可以在线程中创建一个Looper,所以Looper是运行在多线程中的。
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 直接使用了Looper消息循环中的消息队列,保证了数据的一致性,因为一个线程肯定只会有一个消息循环Looper那么肯定也只有一个消息队列MessageQueue
final MessageQueue queue = me.mQueue;
// ...
//然后开始无限循环进行消息处理
for (;;) {
// 关键点【1】获取下一个消息用来处理,可能会被阻塞,阻塞的问题刚在上面进行了简单的分析
Message msg = queue.next(); // might block,从MessageQueue获取待处理的Message(阻塞线程)
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// ...
try {
// 关键点【2】这里非常关键,target我们前面讲过是Handler调用者自身,因此肯定是调用的自身dispatchMessage()
msg.target.dispatchMessage(msg);
// ...
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
} // ....
msg.recycleUnchecked();// 回收Message,供Message.obtain()复用.
}
}
// 这个方法的代码有点多,我们只看关键点就可以了,这里也只是简单分析下阻塞的情况,
Message next() {
// ...
// 只有第一次循环时pendingIdleHandlerCount 才会为-1,这个变量就不详细讲了
int pendingIdleHandlerCount = -1;
// 下次循环的延迟时间即第一个需要处理的Message的延迟时间
int nextPollTimeoutMillis = 0;
for (;;) {
// ...
// 这也是个native方法,大概的作用是根据延迟时间nextPollTimeoutMillis,来决定延迟多久开始再次循环,或者-1时没有消息会阻塞线程,其实就是通过Native层的MessageQueue阻塞nextPollTimeoutMillis毫秒的时间,这里不做过多分析,感兴趣可自行去了解下怎么做到的
nativePollOnce(ptr, nextPollTimeoutMillis);
// 保证线程安全
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
// 关键点【1】 耶,这里的mMessages不就是在enqueueMessage()方法中经过排序消息后第一个需要处理的消息嘛,这下就明白了吧
Message msg = mMessages;
// ...
if (msg != null) {
if (now < msg.when) {
// 需要处理的Message还没有到执行时间,因此计算出当前消息循环需要沉睡多久时间
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 否则的话立刻执行返回本次需要处理的Message消息,并且赋值mBlocked为false即没有阻塞了
mBlocked = false;
if (prevMsg != null) {// 平常发送的一般都是同步消息,这里不分析这种情况,可自行分析
// prevMsg值只有在异步Message的时候才不会为空,因此这一步我们不分析考虑,有兴趣可以自行看源码了解
prevMsg.next = msg.next;
} else {
// 将需要处理下一个Message赋值给缓存的下次需要处理的mMessages,使其下次可以继续执行
mMessages = msg.next;
}
// 清空该消息后面所有的Message消息对象,这个肯定啦,本次的消息都要进行处理了就需要将后面的隔开咯
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
// 最后返回需要处理的Message消息咯
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// ...
if (pendingIdleHandlerCount <= 0) {
// 这里消息队列为空或者待处理的消息未到执行时间,就block阻塞住,等待下一次有消息进来时唤醒消息循环
mBlocked = true;
continue;
}
// ...
}
// ...
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
// 分发消息,代码很简单
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// msg的callback变量,我们可以在Message数据结构中找到是一个Runnable对象,
// 最后追踪过去会发现handleCallback方法调用了message.callback.run();即Runnable的run方法呢
// 那么你可能会问,这个变量到底干啥的,好,我们看下msg的callback这个变量到底什么时候赋值的呢,
// 可以发现Handler几个postXXX方法看到该Runnable的赋值例如post(Runnable r)、postDelayed(Runnable r, long delayMillis)
// 耶,这下是不是很明白了平时我们调用的Handler.postXXX方法的最终流程了吧哈哈哈,并且这些psotXX最终也会调用MessageQueue.enqueueMessage()方法哦哈哈
handleCallback(msg);
} else {
if (mCallback != null) {
// 这里变量其实你可以在Handler的有参构造方法中发现这样的语句:mCallback = callback;,没错,就是你自己使用了一个Callback实例对象进行了处理
if (mCallback.handleMessage(msg)) {
return;
}
}
// 关键点【1】这句就是我们平时复写的处理消息的方法啦哈哈
handleMessage(msg);
}
}
package com.lh.myhandlerlib;
/**
* 消息Message
*
* @author LuoHao
* Created on 2018/2/25 13:28
*/
public class Message {
/**
* 需要发送的Handler
*/
public Handler target;
public String what;
public Message(String what) {
this.what = what;
}
@Override
public String toString() {
return "what: " + what;
}
}
2、消息队列类MessageQueue:
package com.lh.myhandlerlib;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* 简易的消息队列
* 没有延时操作等,这里只是实现其主要思路
* 延时操作源码里面是通过当前时间与延迟的对比处理的,可自行查阅源码
*
* @author LuoHao
* Created on 2018/2/25 13:28
*/
public class MessageQueue {
/**
* 消息队列
*/
private static final int COUNT = 50;
/**
* 使用阻塞队列BlockingQueue解决生产者消费者
* <p>
* 主要作用是效仿android源码native层的唤醒和堵塞消息队列的问题
* android源码使用的native方法nativePollOnce(阻塞),nativeWake(唤醒),这里使用另一种java并发提供的阻塞队列类替换
*/
private final BlockingQueue<Message> mMessageQueue;
public MessageQueue() {
mMessageQueue = new ArrayBlockingQueue<>(COUNT);
}
/**
* enqueue a message
* <p>
* should be called in {@link Handler#sendMessage(Message)}
*
* @param msg message
* @author LuoHao
* Created on 2018/2/26 17:20
*/
public void enqueueMessage(Message msg) {
try {
mMessageQueue.put(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取下一个Message对象
* <p>
* 消息循环时读取
*
* @author LuoHao
* Created on 2018/2/25 14:33
*/
public Message next() {
try {
return mMessageQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
3、消息循环类Looper:
package com.lh.myhandlerlib;
/**
* 简易的消息循环Looper
*
* @author LuoHao
* Created on 2018/2/25 13:28
*/
public class Looper {
/**
* 消息队列
*/
public MessageQueue mQueue;
/**
* ThreadLocal类用来提供线程内部的局部变量。
* 这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量
* 提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程
* <p>
* ThreadLocal解决多线程的并发问题
* 每个线程维护一个 ThreadLocalMap 的映射表,
* 映射表的 key 是 ThreadLocal 实例本身,value 是要存储的副本变量。
* ThreadLocal 实例本身并不存储值,它只是提供一个在当前线程中找到副本值的 key
* <p>
* 每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例本身,
* value是真正需要存储的Object
*/
private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
private Looper() {
mQueue = new MessageQueue();
}
/**
* Lopper 初始化
*
* @author LuoHao
* Created on 2018/2/25 14:04
*/
public static void prepare() {
if (sThreadLocal.get() != null) {
sThreadLocal.remove();
throw new RuntimeException("Only one Looper may be created per Thread");
}
sThreadLocal.set(new Looper());
}
public static Looper myLooper() {
return sThreadLocal.get();
}
/**
* 提供系统动力,让消息队列开始循环工作
*
* @author LuoHao
* Created on 2018/2/25 14:07
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
sThreadLocal.remove();
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this Thread");
}
final MessageQueue queue = me.mQueue;
// 死循环开始遍历消息并分发
for (; ; ) {
// might block
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
System.out.println(" No message indicates that the message queue is quitting.");
return;
}
msg.target.dispatchMessage(msg);
}
}
}
package com.lh.myhandlerlib;
/**
* 简易的消息处理Handler
* <p>
* 手写android 底层Handler机制实现框架
*
* @author LuoHao
* Created on 2018/2/25 13:28
*/
public class Handler {
/**
* 消息队列
*/
private final MessageQueue mQueue;
/**
* 消息循环
*/
private final Looper mLooper;
public Handler() {
this(Looper.myLooper());
}
public Handler(Looper looper) {
mLooper = looper;
// 保证MessageQueue与Looper对应性
mQueue = mLooper.mQueue;
}
/**
* 发送消息
*
* @param msg message
* @author LuoHao
* Created on 2018/2/26 16:40
*/
public final void sendMessage(Message msg) {
// target是Handler本身
// 多线程中保证Handler的一致性,并且这句是在主线程中调用的,
// 当其调用自身的handleMessage()时才会有线程切换的效果
msg.target = this;
// enqueue a message
mQueue.enqueueMessage(msg);
}
/**
* 复写该方法 处理消息
*
* @author LuoHao
* Created on 2018/2/26 16:44
*/
public void handleMessage(Message msg) {
}
/**
* 面向对象设计模式
* 责任链调度原则
*
* @param msg message
*/
public void dispatchMessage(Message msg) {
handleMessage(msg);
}
}
package com.lh.myhandlerlib;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 手写android 底层Handler机制实现框架的测试
*
* @author LuoHao
* Created on 2018/2/26 16:27
*/
public class HandlerFrameTest {
/**
* 线程数量
*/
private static final int THREAD_COUNT = 10;
private static ThreadFactory namedThreadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "common-thread-pool");
}
};
/**
* Common Thread Pool
* <p>
* 手动创建线程池 建议不直接使用Executors.newCachedThreadPool()
*/
private static ExecutorService commonThreadPool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024),
namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
private static ThreadFactory namedThreadFactory2 = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "single-thread-pool");
}
};
/**
* Single Thread Pool
* <p>
* 手动创建线程池代替直接显示new Thread的问题
*/
private static ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1
, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024)
, namedThreadFactory2, new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
// 手写android 底层Handler机制实现框架
Looper.prepare();
final Handler myHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
System.out.println("handle msg: thread:" + Thread.currentThread().getId() +
" Msg: " + msg.toString());
}
};
// 开启多线程
singleThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("singleThreadPool function thread name: " +
Thread.currentThread().getName());
for (int i = 0; i < THREAD_COUNT; i++) {
commonThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("commonThreadPool function thread name: " +
Thread.currentThread().getName());
Message msg = new Message(UUID.randomUUID().toString());
System.out.println("send Msg: thread: " + Thread.currentThread().getId() +
" Msg: " + msg.toString());
myHandler.sendMessage(msg);
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// new Thread().start()
Looper.loop();
// 异常
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
singleThreadPool function thread name: single-thread-pool
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 12 Msg: what: ac2fdf80-2e8e-42fc-b48b-d62bde634f97
handle msg: thread:1 Msg: what: ac2fdf80-2e8e-42fc-b48b-d62bde634f97
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 13 Msg: what: 0438c263-adbf-43f8-927c-9432166b10b8
handle msg: thread:1 Msg: what: 0438c263-adbf-43f8-927c-9432166b10b8
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 14 Msg: what: 1cbb5240-932e-4dcc-9e5e-7dfdd7bc3bfd
handle msg: thread:1 Msg: what: 1cbb5240-932e-4dcc-9e5e-7dfdd7bc3bfd
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 15 Msg: what: c340722a-4d66-4620-8458-86606c07e496
handle msg: thread:1 Msg: what: c340722a-4d66-4620-8458-86606c07e496
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 16 Msg: what: ec749fa9-b9f6-4104-a149-a418e9426435
handle msg: thread:1 Msg: what: ec749fa9-b9f6-4104-a149-a418e9426435
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 12 Msg: what: 5093039f-be23-41c9-9666-2f0a11225918
handle msg: thread:1 Msg: what: 5093039f-be23-41c9-9666-2f0a11225918
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 13 Msg: what: 24c8b5fe-bbaa-48d5-8515-39f638f90f2b
handle msg: thread:1 Msg: what: 24c8b5fe-bbaa-48d5-8515-39f638f90f2b
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 14 Msg: what: 1e4973fd-ba4e-4168-9846-c7f893dfb5ec
handle msg: thread:1 Msg: what: 1e4973fd-ba4e-4168-9846-c7f893dfb5ec
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 15 Msg: what: 6e6d350d-13fa-4366-9da2-e3c7d109c302
handle msg: thread:1 Msg: what: 6e6d350d-13fa-4366-9da2-e3c7d109c302
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 16 Msg: what: 9c11acf0-88d9-4983-9680-8f3eb14f2976
handle msg: thread:1 Msg: what: 9c11acf0-88d9-4983-9680-8f3eb14f2976
singleThreadPool function thread name: single-thread-pool
commonThreadPool function thread name: common-thread-pool
commonThreadPool function thread name: common-thread-pool
commonThreadPool function thread name: common-thread-pool
commonThreadPool function thread name: common-thread-pool
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 14 Msg: what: 610e827c-e909-44af-8196-e158e26a9e80
send Msg: thread: 15 Msg: what: f3c5976f-f297-4840-b474-84e660dde2b2
send Msg: thread: 12 Msg: what: 600de1c3-a523-4079-bf2e-e6481e848c6c
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 12 Msg: what: 69719d30-eabc-4774-83dc-fc3b3c969579
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 12 Msg: what: 2326cc73-66cc-4d73-8c05-045542014824
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 12 Msg: what: a39c84da-5318-4084-bbf2-e6a1e2d6b09b
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 12 Msg: what: 4e2e8724-abc7-475d-b4f6-1c8dec91a21d
handle msg: thread:1 Msg: what: f3c5976f-f297-4840-b474-84e660dde2b2
handle msg: thread:1 Msg: what: 600de1c3-a523-4079-bf2e-e6481e848c6c
handle msg: thread:1 Msg: what: 69719d30-eabc-4774-83dc-fc3b3c969579
handle msg: thread:1 Msg: what: 2326cc73-66cc-4d73-8c05-045542014824
send Msg: thread: 16 Msg: what: 5350e3de-da0f-4d28-b75f-f83e388906c0
send Msg: thread: 13 Msg: what: b6f776f3-18ab-4cc2-b247-4ccf10718841
commonThreadPool function thread name: common-thread-pool
send Msg: thread: 15 Msg: what: dbb4db1f-0649-459a-9142-c7c9fdedc828
handle msg: thread:1 Msg: what: a39c84da-5318-4084-bbf2-e6a1e2d6b09b
handle msg: thread:1 Msg: what: 4e2e8724-abc7-475d-b4f6-1c8dec91a21d
handle msg: thread:1 Msg: what: 610e827c-e909-44af-8196-e158e26a9e80
handle msg: thread:1 Msg: what: 5350e3de-da0f-4d28-b75f-f83e388906c0
handle msg: thread:1 Msg: what: b6f776f3-18ab-4cc2-b247-4ccf10718841
handle msg: thread:1 Msg: what: dbb4db1f-0649-459a-9142-c7c9fdedc828