本文重点解读JDK中的重要概念——线程组(ThreadGroup
)的源码。
1.线程组的树状结构
我们先看一下java.lang.ThreadGroup
的成员变量:
通过JDK源码可以看到,每个ThreadGroup
对象拥有一个父线程组parent
,同时每个ThreadGroup
对象也拥有自己的子线程组集合group[]
和子线程集合threads[]
。
这样,JDK的ThreadGroup
本质上形成了一个线程组和线程的树状结构。如下图:
2.决定线程组父子关系的时机:构造函数
ThreadGroup
提供了4个构造函数(其中2个是public
类型以供程序猿调用),如下图所示:
这4个构造函数的调用链关系如下:
我们接下来逐一分析它们的源码。
2.1.ThreadGroup()
分析第1个无参构造函数,可知:
- 它仅被JVM调用
- 它创建名为
system
的线程组 - 它的父线程组为
null
2.2.ThreadGroup(String name)
分析第2个构造函数,可知:
- 它调用了第3个构造函数
- 它获取了当前线程所在的线程组,作为自己的父线程组
2.3.ThreadGroup(ThreadGroup parent, String name)
分析第3个构造函数,可知:
- 它调用了第3个构造函数
- 它调用了
checkParentAccess
方法,检查了父线程组parent
的权限
2.4.ThreadGroup(Void unused, ThreadGroup parent, String name)
分析第4个构造函数,可知:
- 在这个构造函数中,调用
add
方法,将传入的线程组对象设置为父线程组。
2.5.add(ThreadGroup g)
此方法就是构建线程组父子关系的关键代码:
813行:当前线程组对象的子线程组集合为空,则创建长度为4的子线程组集合
815行:如果当前线程组对象的子线程组集合长度已经满了,则将子线程组集合扩容为2倍
- 注:JDK源码中有很多扩容代码,在空间和时间上进行性能平衡,值得学习
817行:将传入的线程组g,加入到当前线程组对象的子线程组集合
821行:将子线程组集合的游标增1
2.6.测试
我们可以写一段测试代码:
- 我们借助
getParent()
方法,获得指定线程组的父线程组 - 我们借助
list()
方法,打印当前进程中的线程组的树状结构
上述代码的控制台输出如下:
- 上述代码就形成了1个线程组树:
system线程组
>main线程组
>线程组1
>线程组2
3.决定线程父子关系的时机:add
3.1.触发
Thread.start()
方法会调用ThreadGroup.add(Thread t)
方法,目的是将该线程加入对应线程组的子线程组集合
3.2.ThreadGroup.add(Thread t)
此方法就是构建线程和线程组父子关系的关键代码:
892行:当前线程组对象的子线程集合为空,则创建长度为4的子线程集合
894行:如果当前线程组对象的子线程集合长度已经满了,则将子线程集合扩容为2倍
896行:将传入的线程t,加入到当前线程组对象的子线程集合
900行:将子线程集合的游标增1
906行:将未启动的线程数减1
3.3.测试
我们的测试代码如下:
- 创建3个线程:线程1、线程2、线程3
- 线程1:未指定线程组,
Thread
的构造函数会找当前线程对象的父线程对象所属的线程组,指定线程1要加入到该线程组- 注:此处不展开,读者可通过
Thread
构造函数自行分析
- 注:此处不展开,读者可通过
- 线程2:指定线程2要加入到线程组1
- 线程3:指定线程3要加入到线程组2
- start:线程1、线程2、线程3启动,触发了3个线程真正地加入到对应的线程组
上述代码的控制台输出如下:
- 上述代码就形成了1个线程组和线程的树状结构:
4.完整测试代码
|
|
5.参考
|
|