- 背景介绍
- 从Thread的创建流程开始
- 线程创建的起始点init()
- 第二个init2()
- 启动线程,开车啦!
- 黑实验
- 几个常见的线程手段(操作)
- Thread.sleep()那不可告人的秘密
- Thread.yield()究竟隐藏了什么?
- 无处不在的wait()究竟是什么?
- 扒一扒Looper、Handler、MessageQueue之间的爱恨情仇
- 从Looper.prepare()开始
- 创建Handler
- Looper.loop()
- 幕后黑手MessageQueue
- Handler究竟对Message做了什么?
- 另一个疑问?
- 总结
1
背景介绍
我们在Android开发过程中,几乎都离不开线程。但是你对线程的了解有多少呢?它完美运行的背后,究竟隐藏了多少不为人知的秘密呢?线程间互通暗语,传递信息究竟是如何做到的呢?Looper、Handler、MessageQueue究竟在这背后进行了怎样的运作。本期,让我们一起从Thread开始,逐步探寻这个完美的线程链背后的秘密。
注意,大部分分析在代码中,所以请仔细关注代码哦!
2
从Thread的创建流程开始
在这一个环节,我们将一起一步步的分析Thread的创建流程。
话不多说,直接代码里看。
线程创建的起始点init()
第二个init2()
至此,我们的Thread就初始化完成了,Thread的几个重要成员变量都赋值了。
3
启动线程,开车啦!
通常,我们这样了启动一条线程。
那么start()背后究竟隐藏着什么样不可告人的秘密呢?是人性的扭曲?还是道德的沦丧?让我们一起点进start()。探寻start()背后的秘密。
好把,最精华的函数是native的,先当黑盒处理吧。只要知道它能够调用到Thread实例的run()方法就行了。那我们再看看run()方法到底干了什么神奇的事呢?
4
黑实验
上面的实验表明了,我们完全可以用Thread来作为Runnable。
5
几个常见的线程手段(操作)
Thread.sleep()那不可告人的秘密
我们平时使用Thread.sleep()的频率也比较高,所以我们在一起研究研究Thread.sleep()被调用的时候发生了什么。
在开始之前,先介绍一个概念——纳秒。1纳秒=十亿分之一秒。可见用它计时将会非常的精准。但是由于设备限制,这个值有时候并不是那么准确,但还是比毫秒的控制粒度小很多。
通过上面的分析可以知道,使线程休眠的核心方法就是一个Native函数sleep(lock, millis, nanos),并且它休眠的时常是不确定的。因此,Thread.sleep()方法使用了一个循环,每次检查休眠时长是否满足需求。
同时,需要注意一点,如果线程的interruted状态在调用sleep()方法时被设置为true,那么在开始休眠循环前会抛出InterruptedException异常。
Thread.yield()究竟隐藏了什么?
这个方法是Native的。调用这个方法可以提示cpu,当前线程将放弃目前cpu的使用权,和其它线程重新一起争夺新的cpu使用权限。当前线程可能再次获得执行,也可能没获得。就酱。
无处不在的wait()究竟是什么?
大家一定经常见到,不论是哪一个对象的实例,都会在最下面出现几个名为wait()的方法。等待?它们究竟是怎样的一种存在,让我们一起点击去看看。
哎哟我去,都是Native函数啊。
那就看看文档它到底是什么吧。
根据文档的描述,wait()配合notify()和notifyAll()能够实现线程间通讯,即同步。在线程中调用wait()必须在同步代码块中调用,否则会抛出IllegalMonitorStateException异常。因为wait()函数需要释放相应对象的锁。当线程执行到wait()时,对象会把当前线程放入自己的线程池中,并且释放锁,然后阻塞在这个地方。直到该对象调用了notify()或者notifyAll()后,该线程才能重新获得,或者有可能获得对象的锁,然后继续执行后面的语句。
呃。。。好吧,在说明一下notify()和notifyAll()的区别。
- notify()
调用notify()后,对象会从自己的线程池中(也就是对该对象调用了wait()函数的线程)随机挑选一条线程去唤醒它。也就是一次只能唤醒一条线程。如果在多线程情况下,只调用一次notify(),那么只有一条线程能被唤醒,其它线程会一直在 - notifyAll()
调用notifyAll()后,对象会唤醒自己的线程池中的所有线程,然后这些线程就会一起抢夺对象的锁。
6
Looper、Handler、MessageQueue
我们可能过去都写过形如这样的代码:
很多同学知道,在线程中使用Handler时(除了Android主线程)必须把它放在Looper.prepare()和Looper.loop()之间。否则会抛出RuntimeException异常。但是为什么要这么做呢?下面我们一起来扒一扒这其中的内幕。
从Looper.prepare()开始
当Looper.prepare()被调用时,发生了什么?
经过上面的分析,我们已经知道Looper.prepare()调用之后发生了什么。
但是问题来了!sThreadLocal是个静态的ThreadLocal<Looper> 实例(在Android中ThreadLocal的范型固定为Looper)。就是说,当前进程中的所有线程都共享这一个ThreadLocal<Looper>。那么,Looper.prepare()既然是个静态方法,Looper是如何确定现在应该和哪一个线程建立绑定关系的呢?我们接着往里扒。
来看看ThreadLocal的get()、set()方法。
创建Handler
Handler可以用来实现线程间的通行。在Android中我们在子线程作完数据处理工作时,就常常需要通过Handler来通知主线程更新UI。平时我们都使用new
Handler()来在一个线程中创建Handler实例,但是它是如何知道自己应该处理那个线程的任务呢。下面就一起扒一扒Handler。
Looper.loop()
我们都知道,在Handler创建之后,还需要调用一下Looper.loop(),不然发送消息到Handler没有用!接下来,扒一扒Looper究竟有什么样的魔力,能够把消息准确的送到Handler中处理。
从上面的分析可以知道,当调用了Looper.loop()之后,线程就就会被一个for(;;)死循环阻塞,每次等待MessageQueue的next()方法取出一条Message才开始往下继续执行。然后通过Message获取到相应的Handler
(就是target成员变量),Handler再通过dispatchMessage()方法,把Message派发到handleMessage()中处理。
这里需要注意,当线程loop起来是时,线程就一直在循环中。就是说Looper.loop()后面的代码就不能被执行了。想要执行,需要先退出loop。
现在又产生一个疑问,MessageQueue的next()方法是如何阻塞住线程的呢?接下来,扒一扒这个幕后黑手MessageQueue。
幕后黑手MessageQueue
MessageQueue是一个用单链的数据结构来维护消息列表。
可以看到。MessageQueue在取消息(调用next())时,会进入一个死循环,直到取出一条Message返回。这就是为什么Looper.loop()会在queue.next()处等待的原因。
那么,一条Message是如何添加到MessageQueue中呢?要弄明白最后的真相,我们需要调查一下mHandler.post()这个方法。
Handler究竟对Message做了什么?
Handler的post()系列方法,最终调用的都是下面这个方法:
接下来就看看MessageQueue的enqueueMessage()作了什么。
至此,我们已经揭露了Looper、Handler、MessageQueue隐藏的秘密。
另一个疑问?
也许你已经注意到在主线程中可以直接使用Handler,而不需要Looper.prepare()和Looper.loop()。为什么可以做到这样呢?根据之前的分析可以知道,主线程中必然存在Looper.prepare()和Looper.loop()。既然如此,为什么主线程没有被loop()阻塞呢?看一下ActivityThread来弄清楚到底是怎么回事。
注意ActivityThread并没有继承Thread,它的Handler是继承Handler的私有内部类H.class。在H.class的handleMessage()中,它接受并执行主线程中的各种生命周期状态消息。UI的16ms的绘制也是通过Handler来实现的。也就是说,主线程中的所有操作都是在Looper.prepareMainLooper()和Looper.loop()之间进行的。进一步说是在主Handler中进行的。
7
总结
- Android中Thread在创建时进行初始化,会使用当前线程作为父线程,并继承它的一些配置。
- Thread初始化时会被添加到指定/父线程的ThreadGroup中进行管理。
- Thread正真启动是一个native函数完成的。
- 在Android的线程间通信中,需要先创建Looper,就是调用Looper.prepare()。这个过程中会自动依赖当前Thread,并且创建MessageQueue。经过上一步,就可以创建Handler了,默认情况下,Handler会自动依赖当前线程的Looper,从而依赖相应的MessageQueue,也就知道该把消息放在哪个地方了。MessageQueue通过Message.next实现了一个单链表结构来缓存Message。消息需要送达Handler处理,还必须调用Looper.loop()启动线程的消息泵送循环。loop()内部是无限循环,阻塞在MessageQueue的next()方法上,因为next()方法内部也是一个无限循环,直到成功从链表中抽取一条消息返回为止。然后,在loop()方法中继续进行处理,主要就是把消息派送到目标Handler中。接着进入下一次循环,等待下一条消息。由于这个机制,线程就相当于阻塞在loop()这了。
经过上面的揭露,我们已经对线程及其相互之间通讯的秘密有所了解。掌握了这些以后,相信在以后的开发过程中我们可以思路清晰的进行线程的使用,并且能够吸收Android在设计过程中的精华思想。