惊天秘密!从Thread开始,揭露Android线程通讯的诡计和主线程的阴谋
背景介绍
我们在Android开发过程中,几乎都离不开线程。但是你对线程的了解有多少呢?它***运行的背后,究竟隐藏了多少不为人知的秘密呢?线程间互通暗语,传递信息究竟是如何做到的呢?Looper、Handler、MessageQueue究竟在这背后进行了怎样的运作。本期,让我们一起从Thread开始,逐步探寻这个***的线程链背后的秘密。
注意,大部分分析在代码中,所以请仔细关注代码哦!
从Tread的创建流程开始
在这一个环节,我们将一起一步步的分析Thread的创建流程。
话不多说,直接代码里看。
线程创建的起始点init()
- // 创建Thread的公有构造函数,都调用的都是这个私有的init()方法。我们看看到底干什么了。
- /**
- *
- * @param 线程组
- * @param 就是我们平时接触最多的Runnable同学
- * @param 指定线程的名称
- * @param 指定线程堆栈的大小
- */
- private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
- Thread parent = currentThread(); //先获取当前运行中的线程。这一个Native函数,暂时不用理会它怎么做到的。黑盒思想,哈哈!
- if (g == null) {
- g = parent.getThreadGroup(); //如果没有指定ThreadGroup,将获取父线程的TreadGroup
- }
- g.addUnstarted(); //将ThreadGroup中的就绪线程计数器增加一。注意,此时线程还并没有被真正加入到ThreadGroup中。
- this.group = g; //将Thread实例的group赋值。从这里开始线程就拥有ThreadGroup了。
- this.target = target; //给Thread实例设置Runnable。以后start()的时候执行的就是它了。
- this.priority = parent.getPriority(); //设置线程的优先权重为父线程的权重
- this.daemon = parent.isDaemon(); //根据父线程是否是守护线程来确定Thread实例是否是守护线程。
- setName(name); //设置线程的名称
- init2(parent); //纳尼?又一个初始化,参数还是父线程。不急,稍后在看。
- /* Stash the specified stack size in case the VM cares */
- this.stackSize = stackSize; //设置线程的堆栈大小
- tid = nextThreadID(); //线程的id。这是个静态变量,调用这个方法会自增,然后作为线程的id。
- }
第二个init2()
至此,我们的Thread就初始化完成了,Thread的几个重要成员变量都赋值了。
启动线程,开车啦!
通常,我们这样了启动一条线程。
- Thread threadDemo = new Thread(() -> {
- });
- threadDemo.start();
那么start()背后究竟隐藏着什么样不可告人的秘密呢?是人性的扭曲?还是道德的沦丧?让我们一起点进start()。探寻start()背后的秘密。
- //如我们所见,这个方法是加了锁的。原因是避免开发者在其它线程调用同一个Thread实例的这个方法,从而尽量避免抛出异常。
- //这个方法之所以能够执行我们传入的Runnable里的run()方法,是应为JVM调用了Thread实例的run()方法。
- public synchronized void start() {
- //检查线程状态是否为0,为0表示是一个新状态,即还没被start()过。不为0就抛出异常。
- //就是说,我们一个Thread实例,我们只能调用一次start()方法。
- if (threadStatus != 0)
- throw new IllegalThreadStateException();
- //从这里开始才真正的线程加入到ThreadGroup组里。再重复一次,前面只是把nUnstartedThreads这个计数器进行了增量,并没有添加线程。
- //同时,当线程启动了之后,nUnstartedThreads计数器会-1。因为就绪状态的线程少了一条啊!
- group.add(this);
- started = false;
- try {
- nativeCreate(this, stackSize, daemon); //又是个Native方法。这里交由JVM处理,会调用Thread实例的run()方法。
- started = true;
- } finally {
- try {
- if (!started) {
- group.threadStartFailed(this); //如果没有被启动成功,Thread将会被移除ThreadGroup,同时,nUnstartedThreads计数器又增量1了。
- }
- } catch (Throwable ignore) {
- }
- }
- }
好吧,最精华的函数是native的,先当黑盒处理吧。只要知道它能够调用到Thread实例的run()方法就行了。那我们再看看run()方法到底干了什么神奇的事呢?
黑实验
上面的实验表明了,我们完全可以用Thread来作为Runnable。
几个常见的线程手段(操作)
Thread.sleep()那不可告人的秘密
我们平时使用Thread.sleep()的频率也比较高,所以我们在一起研究研究Thread.sleep()被调用的时候发生了什么。
在开始之前,先介绍一个概念——纳秒。1纳秒=十亿分之一秒。可见用它计时将会非常的精准。但是由于设备限制,这个值有时候并不是那么准确,但还是比毫秒的控制粒度小很多。
- //平时我们调用的Thread.sleep(long)***调用到这个方法来,后一个陌生一点的参数就是纳秒。
- //你可以在纳秒级控制线程。
- public static void sleep(long millis, int nanos)
- throws InterruptedException {
- //下面三个检测毫秒和纳秒的设置是否合法。
- if (millis < 0) {
- throw new IllegalArgumentException("millis < 0: " + millis);
- }
- if (nanos < 0) {
- throw new IllegalArgumentException("nanos < 0: " + nanos);
- }
- if (nanos > 999999) {
- throw new IllegalArgumentException("nanos > 999999: " + nanos);
- }
- if (millis == 0
版权声明:
作者:后浪云
链接:https://www.idc.net/help/200634/
文章版权归作者所有,未经允许请勿转载。
THE END