1、ThreadGroup 与 Thread

Java 程序中,默认情况下,新的线程都会被加入到 main 线程所在的 group 中,main 线程的 group 名字同线程名。父子 Thread、父子 ThreadGroup 以及 Thread 以及 ThreadGroup 之间的层次关系,如图所示:

image.png

2、ThreadGroup 详细介绍

2.1、创建 ThreadGroup

ThreadGroup 的构造函数

public ThreadGroup(String name) public ThreadGroup(ThreadGroup parent, String name)

  1. /**
  2. * Constructs a new thread group. The parent of this new group is
  3. * the thread group of the currently running thread.
  4. * <p>
  5. * The <code>checkAccess</code> method of the parent thread group is
  6. * called with no arguments; this may result in a security exception.
  7. *
  8. * @param name the name of the new thread group.
  9. * @exception SecurityException if the current thread cannot create a
  10. * thread in the specified thread group.
  11. * @see java.lang.ThreadGroup#checkAccess()
  12. * @since JDK1.0
  13. */
  14. public ThreadGroup(String name) {
  15. // 默认取当线程所在的线程组设为 this 的父线程组
  16. this(Thread.currentThread().getThreadGroup(), name);
  17. }
  18. /**
  19. * Creates a new thread group. The parent of this new group is the
  20. * specified thread group.
  21. * <p>
  22. * The <code>checkAccess</code> method of the parent thread group is
  23. * called with no arguments; this may result in a security exception.
  24. *
  25. * @param parent the parent thread group.
  26. * @param name the name of the new thread group.
  27. * @exception NullPointerException if the thread group argument is
  28. * <code>null</code>.
  29. * @exception SecurityException if the current thread cannot create a
  30. * thread in the specified thread group.
  31. * @see java.lang.SecurityException
  32. * @see java.lang.ThreadGroup#checkAccess()
  33. * @since JDK1.0
  34. */
  35. public ThreadGroup(ThreadGroup parent, String name) {
  36. this(checkParentAccess(parent), parent, name);
  37. }
  38. private ThreadGroup(Void unused, ThreadGroup parent, String name) {
  39. this.name = name;
  40. this.maxPriority = parent.maxPriority;
  41. this.daemon = parent.daemon;
  42. this.vmAllowSuspension = parent.vmAllowSuspension;
  43. this.parent = parent;
  44. parent.add(this);
  45. }

示例代码:

  1. package com.yj.thread_group;
  2. /**
  3. * @description: 创建线程组
  4. * @author: erlang
  5. * @since: 2021-02-25 23:21
  6. */
  7. public class CreateThreadGroup {
  8. public static void main(String[] args) {
  9. ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
  10. // 只指定了线程组的名字,未指定父 group
  11. // 这里默认父 group 为当前线程所在的 group 即 curTheadGroup
  12. ThreadGroup group1 = new ThreadGroup("Group_1");
  13. System.out.println(group1.getParent() == mainGroup);
  14. // 这里指定了线程组的名字和父线程组
  15. ThreadGroup group2 = new ThreadGroup(group1, "Group_2");
  16. System.out.println(group2.getParent() == group1);
  17. }
  18. }

2.2、复制 Thread 数组到 ThreadGroup 数组

在一个线程组中会加入若干个线程以及子线程组,线程组为我们提供了若干个方法,可以复制出线程和线程组。

2.2.1、复制 Thread 数组

下面两个方法,会将 ThreadGroup 中的 active 线程全部复制到线程数组中,其中 recurse 参数如果为 true,则该方法会将所有子线程组中的活跃线程都递归到线程数组中,

  1. public int enumerate(Thread list[]) {
  2. checkAccess();
  3. return enumerate(list, 0, true);
  4. }
  5. public int enumerate(Thread list[], boolean recurse) {
  6. checkAccess();
  7. return enumerate(list, 0, recurse);
  8. }
  9. // recurse 为 true 时,递归累加子线程组里的存活线程
  10. private int enumerate(Thread list[], int n, boolean recurse) {
  11. int ngroupsSnapshot = 0;
  12. ThreadGroup[] groupsSnapshot = null;
  13. synchronized (this) {
  14. if (destroyed) {
  15. return 0;
  16. }
  17. int nt = nthreads;
  18. if (nt > list.length - n) {
  19. nt = list.length - n;
  20. }
  21. for (int i = 0; i < nt; i++) {
  22. if (threads[i].isAlive()) {
  23. list[n++] = threads[i];
  24. }
  25. }
  26. if (recurse) {
  27. ngroupsSnapshot = ngroups;
  28. if (groups != null) {
  29. groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
  30. } else {
  31. groupsSnapshot = null;
  32. }
  33. }
  34. }
  35. if (recurse) {
  36. for (int i = 0 ; i < ngroupsSnapshot ; i++) {
  37. n = groupsSnapshot[i].enumerate(list, n, true);
  38. }
  39. }
  40. return n;
  41. }

示例代码:

  1. package com.yj.thread_group;
  2. import java.util.concurrent.TimeUnit;
  3. /**
  4. * @description: 复制 Thread 数组
  5. * @author: erlang
  6. * @since: 2021-02-25 23:40
  7. */
  8. public class EnumerateThread {
  9. public static void main(String[] args) throws InterruptedException {
  10. ThreadGroup threadGroup = new ThreadGroup("CustomThreadGroup");
  11. Thread thread = new Thread(threadGroup, () -> {
  12. while (true) {
  13. try {
  14. TimeUnit.SECONDS.sleep(1);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }, "Thread");
  20. thread.start();
  21. TimeUnit.MICROSECONDS.sleep(2000);
  22. ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
  23. Thread[] threads = new Thread[mainGroup.activeCount()];
  24. int enumerates = mainGroup.enumerate(threads);
  25. System.out.println("第一次输出:" + enumerates);
  26. enumerates = mainGroup.enumerate(threads, false);
  27. System.out.println("第二次输出:" + enumerates);
  28. }
  29. }

上面的代码运行结果如下,最后一个输出会比第一个少 1,这是因为代码中将递归 recurse 设置为了 false,CustomThreadGroup 中的线程不会包含在内。

  1. 第一次输出:3
  2. 第二次输出:2

注意:

  • enumerate 方法获取的线程仅仅是个预估值,并不能百分之百的保证当前线程组的活跃线程。比如,在调用复制之后,某个线程结束了生命周期或者新的线程加入了进来,都会导致数据的不准确
  • enumerate 方法的返回值比 threads 的长度真实

2.2.1、复制 ThreadGroup 数组

和前面介绍的复制线程数组类似,下面两个方法,主要用于复制当前 ThreadGroup 的子线程组,同样 recurse 会决定是否以递归的方式复制。

  1. public int enumerate(ThreadGroup list[]) {
  2. checkAccess();
  3. return enumerate(list, 0, true);
  4. }
  5. public int enumerate(ThreadGroup list[], boolean recurse) {
  6. checkAccess();
  7. return enumerate(list, 0, recurse);
  8. }
  9. private int enumerate(ThreadGroup list[], int n, boolean recurse) {
  10. int ngroupsSnapshot = 0;
  11. ThreadGroup[] groupsSnapshot = null;
  12. synchronized (this) {
  13. if (destroyed) {
  14. return 0;
  15. }
  16. int ng = ngroups;
  17. if (ng > list.length - n) {
  18. ng = list.length - n;
  19. }
  20. if (ng > 0) {
  21. System.arraycopy(groups, 0, list, n, ng);
  22. n += ng;
  23. }
  24. if (recurse) {
  25. ngroupsSnapshot = ngroups;
  26. if (groups != null) {
  27. groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
  28. } else {
  29. groupsSnapshot = null;
  30. }
  31. }
  32. }
  33. if (recurse) {
  34. for (int i = 0 ; i < ngroupsSnapshot ; i++) {
  35. n = groupsSnapshot[i].enumerate(list, n, true);
  36. }
  37. }
  38. return n;
  39. }

示例代码:

  1. package com.yj.thread_group;
  2. import java.util.concurrent.TimeUnit;
  3. /**
  4. * @description: 复制 ThreadGroup 数组
  5. * @author: erlang
  6. * @since: 2021-02-25 23:40
  7. */
  8. public class EnumerateThreadGroup {
  9. public static void main(String[] args) throws InterruptedException {
  10. ThreadGroup threadGroup1 = new ThreadGroup("CustomThreadGroup_1");
  11. ThreadGroup threadGroup2 = new ThreadGroup(threadGroup1, "CustomThreadGroup_2");
  12. TimeUnit.MICROSECONDS.sleep(2);
  13. ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
  14. ThreadGroup[] threads = new ThreadGroup[mainGroup.activeGroupCount()];
  15. int enumerates = mainGroup.enumerate(threads);
  16. System.out.println("第一次输出:" + enumerates);
  17. enumerates = mainGroup.enumerate(threads, false);
  18. System.out.println("第二次输出:" + enumerates);
  19. }
  20. }

上面的代码运行结果如下,这是因为 threadGroup1 的父线程组为 currentThreadGroup,而 threadGroup2 的父线程组为 threadGroup1。

  1. 第一次输出:2
  2. 第二次输出:1

2.4、ThreadGroup 操作

2.4.1、ThreadGroup 的基本操作

方法名 说明
activeCount 用于获取线程组中活跃的线程数量,这个值是估计值,并不能保证百分之百准确。原因:该方法会递归获取其他子线程组中的活跃线程数量
activeGroupCount 用于获取线程组中活跃的子线程组数量,这也是一个估计值,该方法也会递归获取所有的子线程组数量
getMaxPriority 用于获取线程组的优先级。默认情况下,线程组的优先级为 10,在该线程组中,所有线程的优先级都不能大于线程组的优先级
getName 获取线程组的名字
getParent 用于获取当前线程组的父线程组,如果父线程组不存在,则会返回 null。比如:system group 的父线程组为 null
list 没有返回值,执行该方法会将线程组中所有的活跃线程信息全部输出到控制台,即 System.out
parentOf(ThreadGroup g) 会判断当前线程组是不是给定线程组的父线程组,另外如果给定的线程组的就是自己本身,那么该方法也会返回 true
setMaxPriority(int pri) 会指定线程组的最大优先级,但最大优先级不能超过父线程组的最大优先级,执行该方法不仅会改变当前线程组的最大优先级,还会改变所有子线程组的最大优先级

示例代码:

  1. package com.yj.thread_group;
  2. import java.util.concurrent.TimeUnit;
  3. /**
  4. * @description: ThreadGroup 方法使用
  5. * @author: erlang
  6. * @since: 2021-03-01 20:29
  7. */
  8. public class ThreadGroupMethod {
  9. public static void main(String[] args) throws InterruptedException {
  10. ThreadGroup threadGroup = new ThreadGroup("ThreadGroup_1");
  11. Thread thread = new Thread(threadGroup, () -> {
  12. while (true) {
  13. try {
  14. TimeUnit.SECONDS.sleep(1);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }, "Thread");
  20. thread.start();
  21. TimeUnit.MICROSECONDS.sleep(1);
  22. ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
  23. System.out.println("activeCount=" + mainGroup.activeCount());
  24. System.out.println("activeGroupCount=" + mainGroup.activeGroupCount());
  25. System.out.println("getMaxPriority=" + mainGroup.getMaxPriority());
  26. System.out.println("getName=" + mainGroup.getName());
  27. System.out.println("mainGroup.getParent=" + mainGroup.getParent());
  28. System.out.println("\r\nlist: ");
  29. mainGroup.list();
  30. System.out.println("threadGroup.getParent=" + threadGroup.getParent());
  31. System.out.println("parentOf=" + mainGroup.parentOf(threadGroup));
  32. System.out.println("parentOf=" + mainGroup.parentOf(mainGroup));
  33. }
  34. }

运行结果如下:

  1. activeCount=3
  2. activeGroupCount=1
  3. getMaxPriority=10
  4. getName=main
  5. mainGroup.getParent=java.lang.ThreadGroup[name=system,maxpri=10]
  6. list:
  7. java.lang.ThreadGroup[name=main,maxpri=10]
  8. Thread[main,5,main]
  9. Thread[Monitor Ctrl-Break,5,main]
  10. java.lang.ThreadGroup[name=ThreadGroup_1,maxpri=10]
  11. Thread[Thread,5,ThreadGroup_1]
  12. threadGroup.getParent=java.lang.ThreadGroup[name=main,maxpri=10]
  13. parentOf=true
  14. parentOf=true

2.4.2、ThreadGroup 的 interrupt

中断一个线程组会导致该线程组中所有的活跃线程都被中断,即该线程组中每一个线程都被设置了 interrupt 标识。下面是 ThreadGroup.interrupt 方法的源码,从源码中可以看出在 interrupt 内部会执行所有线程的 interrupt 方法。

  1. public final void interrupt() {
  2. int ngroupsSnapshot;
  3. ThreadGroup[] groupsSnapshot;
  4. synchronized (this) {
  5. checkAccess();
  6. for (int i = 0 ; i < nthreads ; i++) {
  7. threads[i].interrupt();
  8. }
  9. ngroupsSnapshot = ngroups;
  10. if (groups != null) {
  11. groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
  12. } else {
  13. groupsSnapshot = null;
  14. }
  15. }
  16. for (int i = 0 ; i < ngroupsSnapshot ; i++) {
  17. groupsSnapshot[i].interrupt();
  18. }
  19. }

示例代码:

  1. package com.yj.thread_group;
  2. import java.util.concurrent.TimeUnit;
  3. /**
  4. * @description: 线程组中断
  5. * @author: erlang
  6. * @since: 2021-03-01 21:02
  7. */
  8. public class ThreadGroupInterrupt {
  9. public static void main(String[] args) throws InterruptedException {
  10. ThreadGroup threadGroup = new ThreadGroup("ThreadGroup_1");
  11. new Thread(threadGroup, () -> {
  12. while (true) {
  13. try {
  14. TimeUnit.SECONDS.sleep(1);
  15. } catch (InterruptedException e) {
  16. break;
  17. }
  18. }
  19. System.out.println("thread1 is End!");
  20. }, "thread1").start();
  21. new Thread(threadGroup, () -> {
  22. while (true) {
  23. try {
  24. TimeUnit.SECONDS.sleep(1);
  25. } catch (InterruptedException e) {
  26. break;
  27. }
  28. }
  29. System.out.println("thread2 is End!");
  30. }, "thread2").start();
  31. TimeUnit.MICROSECONDS.sleep(2);
  32. threadGroup.interrupt();
  33. }
  34. }

执行结果:

  1. thread2 is End!
  2. thread1 is End!

2.4.3、ThreadGroup 的 destroy

destroy 用于销毁线程组,该方法只是针对一个没有任何活跃线程的线程组进行一次 destroy 标记,调用该方法的直接结果是在父线程组中将自己移除。

示例代码:

  1. package com.yj.thread_group;
  2. /**
  3. * @description: 线程组的销毁方法
  4. * @author: erlang
  5. * @since: 2021-03-01 21:13
  6. */
  7. public class ThreadGroupDestroy {
  8. public static void main(String[] args) {
  9. ThreadGroup threadGroup = new ThreadGroup("ThreadGroup");
  10. ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
  11. System.out.println("threadGroup.isDestroyed=" + threadGroup.isDestroyed());
  12. mainGroup.list();
  13. threadGroup.destroy();
  14. System.out.println("====================================================");
  15. System.out.println("threadGroup.isDestroyed=" + threadGroup.isDestroyed());
  16. mainGroup.list();
  17. }
  18. }

执行结果:

  1. threadGroup.isDestroyed=false
  2. java.lang.ThreadGroup[name=main,maxpri=10]
  3. Thread[main,5,main]
  4. Thread[Monitor Ctrl-Break,5,main]
  5. java.lang.ThreadGroup[name=ThreadGroup,maxpri=10]
  6. ====================================================
  7. threadGroup.isDestroyed=true
  8. java.lang.ThreadGroup[name=main,maxpri=10]
  9. Thread[main,5,main]
  10. Thread[Monitor Ctrl-Break,5,main]

2.4.4、守护 ThreadGroup

线程可以设置为守护线程,线程组也可以设置为守护线程组,但是若将一个线程组设置为守护线程组(daemon),并不会影响线程的 daemon 属性。如果一个线程组的 daemon 被设置为 true,那么在线程组没有任何活跃的时候该线程组将自动销毁。示例代码:

  1. package com.yj.thread_group;
  2. import java.util.concurrent.TimeUnit;
  3. /**
  4. * @description: 守护线程组
  5. * @author: erlang
  6. * @since: 2021-03-01 21:21
  7. */
  8. public class ThreadGroupDaemon {
  9. public static void main(String[] args) throws InterruptedException {
  10. ThreadGroup threadGroup1 = new ThreadGroup("ThreadGroup_1");
  11. new Thread(threadGroup1, () -> {
  12. try {
  13. TimeUnit.SECONDS.sleep(1);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }, "ThreadGroup_1-thread1").start();
  18. ThreadGroup threadGroup2 = new ThreadGroup("ThreadGroup_2");
  19. new Thread(threadGroup2, () -> {
  20. try {
  21. TimeUnit.SECONDS.sleep(1);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. }, "ThreadGroup_2-thread2").start();
  26. threadGroup2.setDaemon(true);
  27. TimeUnit.SECONDS.sleep(5);
  28. System.out.println("threadGroup1.isDestroyed=" + threadGroup1.isDestroyed());
  29. System.out.println("threadGroup2.isDestroyed=" + threadGroup2.isDestroyed());
  30. }
  31. }

执行结果:

  1. threadGroup1.isDestroyed=false
  2. threadGroup2.isDestroyed=true

3、总结

本文详细介绍了线程组和线程之间的关系,并简单介绍了 ThreadGroup 的大部分的 API。线程组并不是管理线程的,而是针对线程的一个组织。