简介
本文记录了ThreadGroup
的作用和特性,它和线程池的区别。
前言
同事突然问我线程组ThreadGroup
与线程池ThreadPool
有什么区别?又有啥作用?
说实话,确实自己理解的不够清晰,讨论过后整理记录下自己的理解
各自的意义
字面意义:
- 线程池:空闲的线程放在一起管理
- 线程组:对线程进行分组管理
从定义上看:
- 线程池是一种池化实现,为了重复利用空闲的线程资源,避免系统频繁创建线程造成的大量资源消耗。
- 线程组是一个抽象集合,为了统一方便管理多个线程。
当然线程池也可以理解为一类线程的集合,不过这类集合与线程组同样是有区别的:
- 线程池是空闲的线程的集合
- 线程组是所有活动中的线程和线程组的集合
为什么是所有?
JVM
启动时会创建一个名为system
的线程组,它并没有parent
对象。
/**
* Creates an empty Thread group that is not in any Thread group.
* This method is used to create the system Thread group.
*/
private ThreadGroup() { // called from C code
this.name = "system";
this.maxPriority = Thread.MAX_PRIORITY;
this.parent = null;
}
而在JVM
启动完成后,程序运行时创建的ThreadGroup
会放入当前线程所在的线程组,这意味着之后的线程都有一个parent
对象。
public ThreadGroup(String name) {
this(Thread.currentThread().getThreadGroup(), name);
}
public ThreadGroup(ThreadGroup parent, String name) {
this(checkParentAccess(parent), parent, name);
}
private ThreadGroup(Void unused, ThreadGroup parent, String name) {
this.name = name;
this.maxPriority = parent.maxPriority;
this.daemon = parent.daemon;
this.parent = parent;
parent.add(this);
}
而创建Thread
时,默认会加入当前线程所在的线程组。
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// ...
if (g == null) {
if (security != null) {
/* security.getThreadGroup()方法用于在被调用期间返回要在其中创建任何新线程的线程组,否则,当在调用期间没有与其相关的新创建线程时,它将返回当前线程的线程组 */
g = security.getThreadGroup();
}
/* 默认加入父线程所在的线程组 */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
// ...
g.addUnstarted();
this.group = g;
// ...
}
由此可见JVM
启动时会创建一个默认名为system
的线程组,由这个线程组一直往下创建Thread
和ThreadGroup
,能够想象这形成的是一个树结构,根节点便是这个system
的线程组。
为什么时活动中?
线程只有执行start
时才会加入ThreadGroup
,而当其退出则会被移出。
// Thread.class
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
// Thread.class
private void exit() {
if (threadLocals != null && TerminatingThreadLocal.REGISTRY.isPresent()) {
TerminatingThreadLocal.threadTerminated();
}
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
// ThreadGroup.class
void threadStartFailed(Thread t) {
synchronized(this) {
remove(t);
nUnstartedThreads++;
}
}
void threadTerminated(Thread t) {
synchronized (this) {
remove(t);
if (nthreads == 0) {
notifyAll();
}
if (daemon && (nthreads == 0) && (nUnstartedThreads == 0) && (ngroups == 0)) {
destroy();
}
}
}
各自的作用
线程池的作用上面已经说过了,是为了减小系统频繁创建线程带来的资源消耗,而线程组为了统一方便的管理组内多个线程提供很多方法:
返回类型 | 方法 | 描述 |
---|---|---|
void | suspend() | 暂停所有线程 |
void | resume() | 恢复所有线程 |
void | stop() | 停止所有线程 |
void | interrupt() | 中断所有线程 |
void | setMaxPriority(int pri) | 设置最大优先级 |
注: 由于线程安全问题,suspend()、resume() 和 stop() 都已经被标记过时了。
Thread.stop
可能导致数据不一致问题被弃用Thread.suspend
和Thread.resume
也容易导致死锁
上面的方法都是批量修改管理线程的,ThreadGroup
还提供了许多查看状态的方法
int activeCount() // 返回此线程组及其子组中活动线程数的估计值
int activeGroupCount() //返回此线程组及其子组中活动线程组数的估计值
int enumerate(Thread[] list) // 将此线程组及其子组中的每个活动线程复制到指定的数组中
int enumerate(Thread[] list, boolean recurse) // 将此线程组及其子组中的每个活动线程复制到指定的数组中
int enumerate(ThreadGroup[] list) // 将此线程组及其子组中的每个活动线程组复制到指定的数组中
int enumerate(ThreadGroup[] list, boolean recurse) // 将此线程组及其子组中的每个活动线程组复制到指定的数组中
void list() // 将有关此线程组的信息打印到标准输出。
字段属性
public class ThreadGroup implements UncaughtExceptionHandler {
// 父线程组对象
private final ThreadGroup parent;
// 线程组名
String name;
// 最高优先级
int maxPriority;
// 是否已销毁
boolean destroyed;
// 是否守护线程组
boolean daemon;
// 虚拟机自动挂起
boolean vmAllowSuspension;
// 未启动的线程数量
int nUnstartedThreads;
// 已启动的线程总数
int nthreads;
// 已启动的线程数组
Thread[] threads;
// 线程组总数
int ngroups;
// 线程组数组
ThreadGroup[] groups;
// ... methods
}
其中:
maxPriority
不能大于parent.maxPriority
,根线程组的maxPriority
为 10- 还存在运行中的线程时无法调用
destroy
,无法销毁,销毁是递归的 daemon
表示为守护线程组,与线程的daemon
无关,详见 守护线程组new Thread(...)
就会使nUnstartedThreads++
nthreads
是threads
的最后一个元素的下标,当Thread.start()
时加一,并可触发扩容threads
和groups
初始容量为4,2倍数扩大
守护线程组
ThreadGroup
和Thread
都有daemon
属性,ThreadGroup
又可以统一管理Thread
,很容易让人认为ThreadGroup.setDaemon(boolean)
也是循环遍历设置Thread.daemon
,然而并不是这样(之前理解错了 😂)。
在ThreadGroup.setDaemon(boolean)
源码上有一段说明:
/**
* Changes the daemon status of this thread group.
* <p>
* First, the {@code checkAccess} method of this thread group is
* called with no arguments; this may result in a security exception.
* <p>
* A daemon thread group is automatically destroyed when its last
* thread is stopped or its last thread group is destroyed.
*
* @param daemon if {@code true}, marks this thread group as
* a daemon thread group; otherwise, marks this
* thread group as normal.
* @throws SecurityException if the current thread cannot modify
* this thread group.
* @see java.lang.SecurityException
* @see java.lang.ThreadGroup#checkAccess()
* @since 1.0
*/
public final void setDaemon(boolean daemon) {
checkAccess();
this.daemon = daemon;
}
大致意义就是线程组的daemon
属性表示当它内部没有active
的线程时,它会自动销毁。
当一个守护线程组ThreadGroup
不存在活动中的线程或者子线程组时,它会执行销毁方法destroy
:
private void remove(ThreadGroup var1) {
synchronized(this) {
if (!this.destroyed) {
for(int var3 = 0; var3 < this.ngroups; ++var3) {
if (this.groups[var3] == var1) {
--this.ngroups;
System.arraycopy(this.groups, var3 + 1, this.groups, var3, this.ngroups - var3);
this.groups[this.ngroups] = null;
break;
}
}
if (this.nthreads == 0) {
this.notifyAll();
}
if (this.daemon && this.nthreads == 0 && this.nUnstartedThreads == 0 && this.ngroups == 0) {
this.destroy();
}
}
}
}
void threadTerminated(Thread t) {
synchronized (this) {
remove(t);
if (nthreads == 0) {
notifyAll();
}
if (daemon && (nthreads == 0) &&
(nUnstartedThreads == 0) && (ngroups == 0))
{
destroy();
}
}
}
相关
ThreadGroup in Java
Java 并发 之 线程组 ThreadGroup 介绍
Java多线程系列——过期的suspend()挂起、resume()继续执行线程
ThreadGroup