1.ThreadGroup 与 Thread

在Java程序中, 默认情况下, 新的线程都会被加入到main线程所在的group中, main线程的group名字同线程名。如同线程存在父子关系一样, Thread Group同样也存在父子关系。图6-1就很好地说明了父子thread、父子thread Group以及thread和group之间的层次关系
image.png

2.创建ThreadGroup

创建Thread Group的语法如下:

  1. public Thread Group(String name)
  2. public Thread Group(Thread Group parent String name)

创建Thread Group的语法非常简单, 可通过上面某个构造函数来创建, 第一个构造函数为Thread Group赋予了名字, 但是该Thread Group的父Thread Group是创建它的线程所在的Thread Group; 第二个Thread Group的构造函数赋予group名字的同时又显式地指定了父Group。

  1. public class TestThreadGroup {
  2. public static void main(String[] args) {
  3. ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
  4. ThreadGroup group1 = new ThreadGroup("group1");
  5. System.out.println(group1.getParent() == currentGroup);
  6. ThreadGroup group2 = new ThreadGroup(group1, "Group2");
  7. System.out.println(group2.getParent() == group1);
  8. }
  9. }

3.复制Thread数组和ThreadGroup数组

3,1复制Thread数组

  1. public int enumerate(Thread[] list);
  2. public int enumerate(Thread[] list boolean recurse);

上述两个方法, 会将Thread Group中的active线程全部复制到Thread数组中, 其中recurse参数如果为true, 则该方法会将所有子group中的active线程都递归到Thread数组中, enumerate(Thread[] list) 实际上等价于enumerate(Thread[] true) , 上面两个方法都调用了Thread Group的私有方法enumerate:

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

举一例:enumerate方法的使用

  1. import java.util.concurrent.TimeUnit;
  2. public class TestThreadGroup {
  3. public static void main(String[] args) throws InterruptedException {
  4. ThreadGroup myGroup = new ThreadGroup("mygroup");
  5. Thread th = new Thread(
  6. myGroup,
  7. ()-> {
  8. while(true) {
  9. try {
  10. TimeUnit.SECONDS.sleep(1);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }
  16. ,"MyThread");
  17. th.start();
  18. TimeUnit.MILLISECONDS.sleep(2);
  19. ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
  20. Thread[] list = new Thread[mainGroup.activeCount()];
  21. int recuseSize = mainGroup.enumerate(list);
  22. System.out.println(recuseSize);
  23. recuseSize = mainGroup.enumerate(list,false);
  24. System.out.println(recuseSize);
  25. }
  26. }

上面的代码运行之后, 最后一个输出会比第一个少1, 那是因为代码中将递归recurse设置为了false, my Group中的线程将不会包含在内。

3.2 复制ThreadGroup数组

  1. public int enumerate(Thread Group[] list);
  2. public int enumerate(Thread Group[] list boolean recurse);

和复制Thread数组类似, 上述两个方法, 主要用于复制当前Thread Group的子Group,同样recurse会决定是否以递归的方式复制。

  1. import java.util.concurrent.TimeUnit;
  2. public class TestCopyThreadGroup {
  3. public static void main(String[] args) throws InterruptedException {
  4. ThreadGroup myGroup1 = new ThreadGroup("MyGroup1");
  5. ThreadGroup myGroup2 = new ThreadGroup(myGroup1, "MyGroup2");
  6. TimeUnit.MILLISECONDS.sleep(2);
  7. ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
  8. ThreadGroup[] list = new ThreadGroup[mainGroup.activeCount()];
  9. int recurseSize = mainGroup.enumerate(list);
  10. System.out.println(recurseSize);
  11. recurseSize = mainGroup.enumerate(list,false);
  12. System.out.println(recurseSize);
  13. }
  14. }

在代码清单6-3中, my Group 1的父group为main Group, 而my Group 2的父group为my Group 1, 因此上述的代码运行之后, 递归复制的结果为2, 不递归的情况下为1。

4.ThreadGroup操作

4.1ThreadGroup的基本操作

  • activeCount() 用于获取group中活跃的线程, 这只是个估计值, 并不能百分之百地保证数字一定正确, 原因前面已经分析过, 该方法会递归获取其他子group中的活跃线程。
  • activeGroupCount() 用于获取group中活跃的子group, 这也是一个近似估值, 该方法也会递归获取所有的子group。
  • getMaxPriority() 用于获取group的优先级,默认情况下,Group的优先级为10,在该group中, 所有线程的优先级都不能大于group的优先级。
  • getName() 用于获取group的名字。
  • getParent() 用于获取group的父group, 如果父group不存在, 则会返回null, 比如system group的父group就为null。
  • list() 该方法没有返回值, 执行该方法会将group中所有的活跃线程信息全部输出到控制台, 也就是System.out。
  • parentOf(Thread Group g) 会判断当前group是不是给定group的父group, 另外如果给定的group就是自己本身,那么该方法也会返回true。
  • setMaxPriority(int pri) 会指定group的最大优先级, 最大优先级不能超过父group的最大优先级, 执行该方法不仅会改变当前group的最大优先级, 还会改变所有子group的最大优先级
  1. import java.util.concurrent.TimeUnit;
  2. public class ThreadGroupBasic {
  3. public static void main(String[] args) {
  4. ThreadGroup group = new ThreadGroup("group1");
  5. Thread thread = new Thread(
  6. group,
  7. () -> {
  8. while(true) {
  9. try {
  10. TimeUnit.SECONDS.sleep(1);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }
  16. , "thread");
  17. thread.setDaemon(true);
  18. thread.start();
  19. ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
  20. System.out.println("activeCount=" + mainGroup.activeCount());
  21. System.out.println("activeGroupCount=" + mainGroup.activeGroupCount());
  22. mainGroup.list();
  23. System.out.println("--------------------------");
  24. System.out.println("parentOf=" + mainGroup.parentOf(group));
  25. }
  26. }

4.2 ThreadGroup的interrupt

interrupt一个thread group会导致该group中所有的active线程都被interrupt, 也就是说该group中每一个线程的interrupt标识都被设置了, 下面是Thread Group 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. }

interrupt方法案例:

  1. import java.util.concurrent.TimeUnit;
  2. public class TestThreadInterrupt {
  3. public static void main(String[] args) throws InterruptedException {
  4. ThreadGroup group = new ThreadGroup("TestGroup");
  5. new Thread(
  6. group,
  7. () -> {
  8. while(true) {
  9. try {
  10. TimeUnit.MILLISECONDS.sleep(2);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }
  16. ,"t1").start();
  17. new Thread(group,
  18. ()-> {
  19. try {
  20. TimeUnit.MILLISECONDS.sleep(1);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. ,"t2").start();
  26. TimeUnit.MILLISECONDS.sleep(2);
  27. group.interrupt();
  28. }
  29. }

4.3ThreadGroup的destroy

destroy用于销毁Thread Group, 该方法只是针对一个没有任何active线程的group进行一次destroy标记, 调用该方法的直接结果是在父group中将自己移除:
image.png
测试代码:

  1. public class ThreadGroupDestroy {
  2. public static void main(String[] args) {
  3. ThreadGroup group = new ThreadGroup("TestGroup");
  4. ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
  5. System.out.println("group.isDestroyed=" + group.isDestroyed());
  6. mainGroup.list();
  7. group.destroy();
  8. System.out.println("group.isDestroyed=" + group.isDestroyed());
  9. mainGroup.list();
  10. }
  11. }

image.png

4.4守护ThreadGroup

线程可以设置为守护线程, Thread Group也可以设置为守护Thread Group, 但是若将一个Thread Group设置为daemon, 也并不会影响线程的daemon属性, 如果一个Thread Group的daemon被设置为true, 那么在group中没有任何active线程的时候该group将自动destroy, 下面我们给出一个简单的例子来对其进行说明:

  1. import java.util.concurrent.TimeUnit;
  2. public class ThreadGroupDaemon {
  3. public static void main(String[] args) throws InterruptedException {
  4. ThreadGroup group = new ThreadGroup("Group1");
  5. new Thread(
  6. group,
  7. ()-> {
  8. try {
  9. TimeUnit.SECONDS.sleep(1);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. ,"group1-thread1").start();
  15. ThreadGroup group2 = new ThreadGroup("Group2");
  16. new Thread(
  17. group2,
  18. ()-> {
  19. try {
  20. TimeUnit.SECONDS.sleep(1);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. ,"group2-thread1").start();
  26. group2.setDaemon(true);
  27. TimeUnit.SECONDS.sleep(3);
  28. System.out.println(group.isDestroyed());
  29. System.out.println(group2.isDestroyed());
  30. }
  31. }

在上面的代码中, 第二个group的daemon被设置为true, 当其中没有active线程的时候, 该group将会自动被destroy, 而第一个group则相反。