Android开发经验谈Android开发Android技术知识

Thread的老管家ThreadGroup(二)

2018-10-12  本文已影响5人  k_conn

大家好我是kconn,我是一个不爱看源码,不喜欢分析源码,更不喜欢写文章的程序员。自从面试被人虐后,我知道我的不足,所以我打算学习Android源码。

最近有感而发想给大家分享下我的鸡汤:“人总是有梦想的,如果不为此拼命努力,怎么会知道梦想是那么遥不可及。”

我麦镇楼!

本篇文章是上一篇文章的补充,上篇文章写的有点过于随意,很多细节比较松散,让人看的云里雾里,所以特地抽时间写了这篇文章,让更多的人了解老管家ThreadGroup。

因为是文章的补充,所以源码比较少,没有看过上篇文章的同学可以先去看看了解下。好了,废话不多说,直接进入主题。

ThreadGroup俗称线程组,位于java.lang包下的一个类,是用于统一的线程管理,听名字就知道,相当于是线程的集合。除了系统线程组外,其他的线程组都一定会有父线程组,他们之间是树的关系。

口说无凭,我先上代码给你们肛。

    ThreadGroup tomThreadGroup = new ThreadGroup("Tom");

我创建了一个名字为"汤姆"的线程组。那么看下他和他的父线程组。

    Log.e("logTag", "tomThreadGroup名:" + leeThreadGroup.getName()); // 输出Tom
    Log.e("logTag", "tom的父线程组大名:" + leeThreadGroup.getParent().getName()); // 输出main

接着看下父线程组的父线程组,也就是爷爷线程组。

    Log.e("logTag", "Tom的爷爷线程组大名:" + leeThreadGroup.getParent().getParent().getName()); // 输出system

最后看这个“system”的线程组的父线程组,输出的时候崩掉了,错误日志显示空指针。

看过我上篇文章的同学就知道,这个“system”线程组是最强王者,上面没人了,所以父线程组为空。

资料里面说线程组之间名字可重复,那么我们试试看。

    ThreadGroup tomThreadGroup = new ThreadGroup("Tom");
    ThreadGroup tomThreadGroup2 = new ThreadGroup("Tom");
    
    Log.e("logTag", "tomThreadGroup名:" + leeThreadGroup.getName()); // 输出Tom
    Log.e("logTag", "tomThreadGroup2名:" + leeThreadGroup2.getName()); // 输出Tom

写到这里,我很好奇tomThreadGroup和tomThreadGroup2是mainThreadGroup的子线程组,那么mainThreadGroup的子线程组中是否只有他们呢?为了防止混淆,我把tomThreadGroup2的名字改为Jerry。

    ThreadGroup tomThreadGroup = new ThreadGroup("Tom");
    ThreadGroup jerryThreadGroup = new ThreadGroup("Jerry");
    
    ThreadGroup mainThreadGroup = tomThreadGroup.getParent();
    // 定义一个线程组数组,大小为mainThreadGroup活动的线程组数量(包括子线程组合孙子线程组)
    ThreadGroup[] threadGroups = new ThreadGroup[mainThreadGroup.activeGroupCount()];
    // 复制mainThreadGroup的所有活动子线程组和孙子线程组
    mainThreadGroup.enumerate(threadGroups);
    // 输出线程组信息
    for (ThreadGroup threadGroup : threadGroups) {
        Log.e("logTag", threadGroup.getName()); // 两个结果Jerry、Tom
    }

既然知道了main那么也可以知道system线程组里面的子线程组。

    ThreadGroup systemThreadGroup = mainThreadGroup.getParent();
    
    ThreadGroup[] threadGroups = new ThreadGroup[systemThreadGroup.activeGroupCount()];
    
    systemThreadGroup.enumerate(threadGroups);
    
    for (ThreadGroup threadGroup : threadGroups) {
        Log.e("logTag", threadGroup.getName()); // 三个结果main、Jerry、Tom
    }

ok,线程组之间的关系了解完了,现在接着看线程组和线程的关系。

    Thread tuffyThread = new Thread(jerryThreadGroup, "Tuffy");
    
    Log.e("logTag", "Tuffy所属的线程组:" + tuffyThread.getThreadGroup().getName());// 输出结果是Jerry

然后试下输出Jerry的线程

    Thread[] threads = new Thread[jerryThreadGroup.activeCount()];
    jerryThreadGroup.enumerate(threads);
    for (Thread thread : threads) {
        Log.e("logTag", thread.getName()); // 输出结果为空白
    }

我蒙蔽了一会,然后醒悟了,我们获取的是Jerry的活动线程,因为Tuffy还没启动,所以他名下没有活动的线程。这情况就像是你去买房,人家还没过户给你,你就先把钱给了人家。

后面start线程之后输出是Tuffy。

这里插个嘴:看过源码的同学也知道,ThreadGroup里面有个list方法,是用来打印线程组的信息,感兴趣的同学可以自己试下。

同理我们可以得出main和system线程组的活动线程。

mian线程组的线程:Binder_2、Binder_1、main(主线程)、Tuffy

system线程组的线程:HeapTaskDaemon、FinalizerWatchdogDaemon、FinalizerDaemon、
ReferenceQueueDaemon、JDWP、Signal Catcher、Binder_2、Binder_1、main、Tuffy

前面说了,线程组之间是树的关系,那么线程组和线程之间就是集合关系,用以下图来表示。

这里插个嘴:资料上面说,如果线程归入某线程组,那么他就不能改投另一个线程组。就像猫和老鼠,Tom猫是一个线程组,Jerry鼠是另一个线程组。现在有一个线程Tuffy,它加入了Jerry组,那么它就无法改投Tom组。另外再说句,如果线程没有表示加入哪个线程组,那么它默认是属于main线程组。

好,接着看ThreadGroup的interrupt方法,资料上面显示这个是中断该线程组中所有线程的方法。实际上它并没有中断。

    Thread tuffyThread = new Thread(jerryThreadGroup, new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (i == 3) {
                    jerryThreadGroup.interrupt();
                }
                Log.e("logTag", "爆炸虚出:" + i); // 依然我行我素输出到9
            }
        }
    }, "Tuffy");

这里就不太明白,说好的中断呢?在源码中它最后是遍历了它名下的线程,然后调用Thread的interrupt方法(最后实现是native方法)。

百思不得其姐的时候,突然发现这个Thread.sleep抛的异常是InterruptedException,寻思着两者之间会不会有什么骚操作。

    Thread tuffyThread = new Thread(jerryThreadGroup, new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Log.e("logTag", "异常虚出:" + e.toString()); 
                }
                if (i == 3) {
                    jerryThreadGroup.interrupt();
                }
                Log.e("logTag", "爆炸虚出:" + i); // 依然我行我素输出到9
            }
        }
    }, "Tuffy");

结果可以看到当虚出到3的时候抛了异常。这里我说下细节,虚出3、java.lang.InterruptedException、4这三个动作是一起完成的,也就是说明此刻的Thread.sleep(1000)是没效的,也就是说interrupt方法中断的是Thread.sleep方法。值得一提的是它只中断一次就是抛异常的那次,后面的5-9依然sleep了1000毫秒。至于为什么,这就留到后面写到Thread文章再详细解释。

好了,最后在结束的之前介绍下Thread和ThreadGroup异常的处理。ThreadGroup实现了Thread.UncaughtExceptionHandler接口,用意在于当线程组中的线程发生异常,那么就会调用Thread.UncaughtExceptionHandler接口的uncaughtException方法。异常的处理我们可以调用线程的setUncaughtExceptionHandler方法进行处理,也可以重写线程组的uncaughtException方法处理。

    ThreadGroup jerryThreadGroup = new ThreadGroup("Jerry") {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            Log.e("logTag", t.getName() + ":" + e.toString()); // Tuffy:java.lang.RuntimeException: 对方不想理你并向你抛出个异常
        }
    };
    
    Thread tuffyThread = new Thread(jerryThreadGroup, new Runnable() {
        @Override
        public void run() {
            throw new RuntimeException("对方不想理你并向你抛出个异常");
        }
    },"Tuffy");

肛源码不容易,我也不是大牛,文章也写的不是很好。大家看着有什么想法都可以说,我不一定会看,看了也不一定会回,因为我胃疼!


上一篇下一篇

猜你喜欢

热点阅读