简介

本文记录了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的线程组,由这个线程组一直往下创建ThreadThreadGroup,能够想象这形成的是一个树结构,根节点便是这个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();
        }
    }
}

各自的作用

线程池的作用上面已经说过了,是为了减小系统频繁创建线程带来的资源消耗,而线程组为了统一方便的管理组内多个线程提供很多方法:

返回类型方法描述
voidsuspend()暂停所有线程
voidresume()恢复所有线程
voidstop()停止所有线程
voidinterrupt()中断所有线程
voidsetMaxPriority(int pri)设置最大优先级

注: 由于线程安全问题,suspend()、resume() 和 stop() 都已经被标记过时了。

Thread.stop可能导致数据不一致问题被弃用
Thread.suspendThread.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++
  • nthreadsthreads的最后一个元素的下标,当Thread.start()时加一,并可触发扩容
  • threadsgroups初始容量为4,2倍数扩大

守护线程组

ThreadGroupThread都有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