“全栈2019”Java多线程第十三章:线程组ThreadGroup详解

难度

初级

学习时间

10分钟

适合人群

零基础

开发语言

Java

开发环境

  • JDK v11
  • IntelliJ IDEA v2018.3

友情提示

  • 本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。
  • 本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!

1.温故知新

前面在《“全栈2019”Java多线程第五章:线程睡眠sleep()方法详解》一章中介绍了如何暂时停止执行线程。

《“全栈2019”Java多线程第六章:中断线程interrupt()方法详解》一章中介绍了如何停止线程。

《“全栈2019”Java多线程第七章:等待线程死亡join()方法详解》一章中介绍了如何让一个线程等待另一个线程执行完毕再执行

《“全栈2019”Java多线程第八章:放弃执行权yield()方法详解》一章中介绍了如何让一个线程放弃执行权。

《“全栈2019”Java多线程第九章:判断线程是否存活isAlive()详解》一章中介绍了如何判断一个线程是否存活

《“全栈2019”Java多线程第十章:Thread.State线程状态详解》一章中介绍了线程的6种状态

《“全栈2019”Java多线程第十一章:线程优先级详解》一章中介绍了如何设置/获取线程的优先级

《“全栈2019”Java多线程第十二章:后台线程setDaemon()方法详解》一章中介绍了如何将一个线程设置为后台线程

现在我们来讲解线程组ThreadGroup

2.什么是线程组?

既然叫“线程组”,那么根据字母意思就是由多个线程组成一个线程组。

所以线程组的定义来了:

线程组表示一组线程。

线程组有用对应的Java类来描述吗?

有。就是ThreadGroup。下面有讲解。

3.为什么要有线程组?

从一些比较权威的Java教程书来看:

之所以要提出“线程组”的概念,一般认为,是由于“安全”或者“保密”方面的理由。根据Arnold和Gosling的说法:“线程组中的线程可以修改组内的其他线程,包括那些位于分层结构最深处的。一个线程不能修改位于自己所在组或者下属组之外的任何线程。”

不过,我认为线程组还有一个作用是可以批量操作和管理线程

4.初次见线程组

下面,我们以最快的方式查看线程组。

演示:

请打印当前线程组的线程组。

请观察程序代码及结果。

代码:

Main类:

结果:

从运行结果来看,我们好像还看不到线程组的身影。

首先,我们先来看看程序代码,代码很简单,打印当前线程:

结果我们刚刚也看到了,下面来解释控制台输出结果是什么意思。

Thread其实就是一个字符串,要非说它有什么意义的话,它只能表示这是一个线程类。

后面中括号[]里面的内容中第一个main表示当前线程名称。

中间的数字5表示当前线程的优先级。

最后的main表示当前线程所属哪个线程组,如当前主线程所属main线程组。

好在接触了线程组的身影,不过这一点还算不上什么。下面我们来一步一步探索线程组。

5.线程的toString()方法

我们知道上述程序中直接打印对象,实际上就是调用对象的toString()方法。于是,我们就来看看Thread的toString()方法:

将注释翻译成中文:

去掉注释版:

toString()方法的作用是返回此线程的字符串表示形式,包括线程的名称,优先级和线程组。

我们从toString()方法中可以看到在方法的内部有获取线程组的痕迹:

然后,也可以看到它最后打印结果是如何组成的:

下面,我们就看看这个获取线程组的方法:getThreadGroup()。

6.获取线程组getThreadGroup()方法

getThreadGroup()方法在Thread类中的源码:

将注释翻译成中文:

去掉注释版:

getThreadGroup()方法的作用就是返回此线程所属的线程组。如果此线程已死(已停止),则此方法返回null。

访问权限

public:getThreadGroup()方法访问权限是公开的。

final:getThreadGroup()方法是最终的。不能被子类重写。

ThreadGroup:getThreadGroup()方法返回此线程的线程组。

getThreadGroup()方法只能被对象调用。

参数

无。

抛出的异常

无。

应用

我们来演示如何使用getThreadGroup()方法获取一个线程的线程组。

演示:

请使用getThreadGroup()方法获取线程的线程组。

请观察程序代码及结果。

代码:

Main类:

结果:

从运行结果来看,我们打印了线程组ThreadGroup对象,也就是调用了它的toString()方法。

java.lang.ThreadGroup表示该对象是java.lang.ThreadGroup类型。

name=main表示该线程组的名称是main。

maxpri=10表示该线程组的优先级的是10。

7.线程组ThreadGroup

在Java中,我们也用类将线程组进行了描述,这个类就是ThreadGroup

ThreadGroup一共有四个构造方法:

两个私有的构造方法:

两个公开的构造方法:

那么我们就只能使用公开的构造方法:

  • ThreadGroup​(String name)
  • ThreadGroup​(ThreadGroup parent, String name)

8.创建线程组ThreadGroup​(String name)

构造方法ThreadGroup​(String name)在ThreadGroup中的源码:

将注释翻译成中文:

去掉注释版:

下面,我们就来使用ThreadGroup​(String name)构造方法来构造一个线程组:

然后,打印刚刚创建出来的线程组:

运行程序,执行结果:

从运行结果来看,符合预期。和我们在第6小节打印的结果一致。

线程组名称也是我们自己命名的。

下面我们来看看ThreadGroup​(String name)方法内部是怎么实现的。

为什么要去看看ThreadGroup​(String name)方法内部实现呢?

因为ThreadGroup​(String name)方法内部它调用了ThreadGroup另一个重载方法ThreadGroup​(ThreadGroup parent, String name),所以来看看它内部实现。

大家应该可以看到,我们ThreadGroup​(String name)方法内部它调用了ThreadGroup另一个重载方法ThreadGroup​(ThreadGroup parent, String name),传递的第一个参数就是获取当前线程的线程组:

既然如此,下面我们就看看构造线程组的另一个方法:ThreadGroup​(ThreadGroup parent, String name)。

9.创建线程组ThreadGroup​(ThreadGroup parent, String name)

上一小节我们演示了使用ThreadGroup​(String name)方法构造线程组,也知道ThreadGroup​(String name)方法内部实际上是调用了ThreadGroup​(ThreadGroup parent, String name)方法。

接下来,我们来看看ThreadGroup​(ThreadGroup parent, String name)方法。

ThreadGroup​(ThreadGroup parent, String name)方法在ThreadGroup类中源码:

将注释翻译成中文:

去掉注释版:

我们可以通过ThreadGroup​(ThreadGroup parent, String name)方法创建一个线程组,并指定父线程组和线程组名称。

下面,就来演示通过ThreadGroup​(ThreadGroup parent, String name)方法来创建一个线程组。

首先,我们创建一个父线程组:

然后,我们创建子线程组:

接着,打印刚刚创建好的线程组:

运行程序,执行结果:

从运行结果来看,符合预期。

现在线程组也创建了,父线程组和子线程组都创建了。子线程组怎么和父线程组呢?

下面,我们就来介绍如何获取父线程组。

10.获取父线程组getParent()方法

我们可以通过线程组的getParent()方法来获取父线程组。

getParent()方法在ThreadGroup类中源码:

将注释翻译成中文:

去掉注释版:

getParent()方法作用是获取线程组的父线程组。

访问权限

public:getParent()方法访问权限是公开的。

final:getParent()方法是最终的。不能被子类重写。

ThreadGroup:getParent()方法返回类型是ThreadGroup类型。

getParent()方法只能被对象调用。

参数

无。

抛出的异常

无。

应用

首先,我们创建一个父线程组:

然后,我们创建一个子线程组:

接着,我们使用getParent()方法获取子线程组的父线程组:

运行程序,执行结果:

从运行结果来看,符合预期。

我们的子线程组taskB的父线程组就是taskA。

11.顶级线程组-系统线程组

我们先来看看主线程所属的线程组:

运行程序,执行结果:

然后,我们来看看mian线程组的父线程组:

运行程序,执行结果:

原来,我们main线程组的父线程组是system线程组,即系统线程组。

我们再来看看系统线程组的父线程组:

主线程的线程组的父线程组:

运行程序,执行结果:

于是,我们就知道了在整个线程组体系中,位于顶级的是系统线程组:

上述图描述了线程组体系。

12.判断一个线程组是不是某个线程组的父线程组parentOf​(ThreadGroup g)方法

如何判断一个线程组是不是某个线程组的父线程组呢?下面我们再来介绍一个方法。

我们可以通过线程组的parentOf​(ThreadGroup g)方法来判断一个线程组是不是某个线程组的父线程组。

parentOf​(ThreadGroup g)方法在ThreadGroup类中的源码:

将注释翻译成中文:

去掉注释版:

parentOf​(ThreadGroup g)方法作用是判断一个线程组是不是某个线程组的父线程组。

访问权限

public:parentOf​(ThreadGroup g)方法访问权限是公开的。

final:parentOf​(ThreadGroup g)方法是最终的。不能被子类重写。

boolean:parentOf​(ThreadGroup g)方法返回boolean类型的值。如果此线程组是判断的线程组的父线程组返回true,否则返回false。

parentOf​(ThreadGroup g)方法只能被对象调用。

参数

ThreadGroup g:ThreadGroup类型。用于判断的子线程组。

抛出的异常

无。

应用

首先,我们创建一个父线程组taskA:

然后,我们创建一个子线程组taskB:

接着,我们调用子线程组taskA的parentOf​(ThreadGroup g)方法并将taskB线程组作为子线程组传入:

运行程序,执行结果:

从运行结果来看,我们线程组taskA是taskB的父线程组。

这里希望大家千万别弄错了,要传的参数是子线程组,不是父线程组

13.获取线程组的名称getName()方法

我们可以通过线程组的getName()方法来获取该线程组的名称。

getName()方法在ThreadGroup类中的源码:

将注释翻译成中文:

去掉注释版:

getName()方法作用是返回线程组的名称。

访问权限

public:getName()方法访问权限是公开的。

final:getName()方法是最终的。不能被子类重写。

void:getName()方法无返回值。

getName()方法只能被对象调用。

参数

无。

抛出的异常

无。

应用

首先,我们创建一个线程组:

然后,我们获取该线程组的名称:

运行程序,执行结果:

从运行结果来看,符合预期。

14.线程与线程组

说了好多关于线程组的方法,那么怎么让线程和线程组关联起来呢?

实际上线程与线程组关联是在我们创建线程的时候:

怎么说?创建线程的时候?

我们需要去查看Thread的构造方法源码:

上面是线程无参构造方法,方法内部调用了Thread(ThreadGroup group, Runnable target, String name,long stackSize)方法:

然后,上面方法内部又调用了Thread(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals)方法:

这个方法有点长,我们来一部分一部分看。

首先是判断线程名称是否为null,如果为null则抛出异常:

然后,记录该线程名称:

接着,获取了当前线程:

然后,判断新建的线程所属的线程组是不是为null:

如果为null,则用引用g记录该线程组:

最后,我们新建的线程所属的线程组就是刚刚记录的线程组g:

从源码角度来看,我们线程与线程组是在创建线程的时候就关联起来了。

总结

  • 线程组表示一组线程。
  • 之所以要提出“线程组”的概念,一般认为,是由于“安全”或者“保密”方面的理由。根据Arnold和Gosling的说法:“线程组中的线程可以修改组内的其他线程,包括那些位于分层结构最深处的。一个线程不能修改位于自己所在组或者下属组之外的任何线程。”
  • 线程组还有一个作用是可以批量操作和管理线程。
  • getThreadGroup()方法的作用就是返回此线程所属的线程组。如果此线程已死(已停止),则此方法返回null。
  • 通过ThreadGroup​(String name)创建一个线程组并指定线程组的名称。
  • 通过ThreadGroup​(ThreadGroup parent, String name)创建一个线程组并指定父线程组和名称。
  • getParent()方法作用是获取线程组的父线程组。
  • 系统线程组是线程组体系中的顶级线程组。
  • parentOf​(ThreadGroup g)方法作用是判断一个线程组是不是某个线程组的父线程组。
  • getName()方法作用是返回线程组的名称。
  • 线程与线程组是在创建线程的时候就关联起来了。

至此,Java中线程组ThreadGroup相关内容讲解先告一段落,更多内容请持续关注。

答疑

如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。

上一章

“全栈2019”Java多线程第十二章:后台线程setDaemon()方法详解

下一章

“全栈2019”Java多线程第十四章:线程与堆栈详解

学习小组

加入同步学习小组,共同交流与进步。

  • 方式一:关注头条号Gorhaf,私信“Java学习小组”。
  • 方式二:关注公众号Gorhaf,回复“Java学习小组”。

全栈工程师学习计划

关注我们,加入“全栈工程师学习计划”。

版权声明

原创不易,未经允许不得转载!

了解更多
举报
评论 0