线程组 Thread Group

目录

前言

正文

1.线程对象关联线程组:一级关联 

2.线程对象关联线程组:多级关联 

3.线程组自动归属特性 

4.获取根线程组 

5.线程组内加线程组  

6.组内的线程批量停止 

7.递归取得与非递归取得组内对象 

8. Thread.activeCount() 方法的使用 

9. Thread.enumerate(Thread tarray[]) 方法的使用

10.补充  

总结


前言

为了方便对某些具有相同功能的线程进行管理,我们可以把线程归属到某一个线程组。线程组中可以有线程对象、线程,类似于树的形式。

线程组 Thread Group_第1张图片


正文

线程组的作用是可以批量地管理线程或线程对象,有效地对线程或线程对象进行组织 。

1.线程对象关联线程组:一级关联 

所谓的一级关联就是父对象中有子对象,但并不创建子孙对象,这种情况经常出现在开发中。

public class groupAddThread {
    static class Task implements Runnable{
        @Override
        public void run() {
            try{
                while(!Thread.currentThread().isInterrupted()){
                    System.out.println("ThreadName"+Thread
                            .currentThread()
                            .getName());
                    Thread.sleep(3000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Task task = new Task();
        ThreadGroup group = new ThreadGroup("shenyang的线程组");
        Thread athread = new Thread(group,task);
        Thread bthread = new Thread(group,task);
        athread.start();
        bthread.start();
        System.out.println("活动的线程数为:"+group.activeCount());
        System.out.println("线程组的名称为:"+group.getName());
    }
}

在代码中将 athread 和 bthread 对象与线程组关联,然后对 athread 和 bthread 对象执行 start() 方法,之后执行 task 中的 run() 方法。

程序结果如图:

线程组 Thread Group_第2张图片

控制台打印出线程组中的 2 个线,以及线程组的名称。另外,2 个线程一直运行,并且每隔 3 秒打印日志。  

2.线程对象关联线程组:多级关联 

所谓多级关联就是父对象中有子对象,子对象中再创建子对象,也就出现了子孙对象。但是,这种写法在开发中并不常见。设计非常复杂的线程树结构不利于线程对象的管理,但 JDK 支持多级关联的线程结构。实现多级关联的关键是 ThreadGroup 类的构造方法: 

public ThreadGroup(ThreadGroup parent, String name) 

新建测试用例

public class groupAddThreadMethodLevel {
    public static void main(String[] args) {
        //在 main 组中添加线程组 A,然后在线程组 A 添加线程对象 z。
        //方法 activeGroupCount() 和 activeCount()的值不是固定的。
        //而是系统的一个快照
        ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
                                //取得 main 主线程所在的线程组
        ThreadGroup group = new ThreadGroup(mainGroup,"A");
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("runMethod!");
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread newThread = new Thread(group,runnable);
        newThread.setName("z");
        newThread.start();
        //线程必须是程序启动后才归属到线程组 A 中,因为在调用 start() 方法时会调用
        //group.add(this);
        ThreadGroup[] listGroup = new ThreadGroup[Thread.currentThread()
                .getThreadGroup().activeGroupCount()];
        Thread.currentThread().getThreadGroup().enumerate(listGroup);
        System.out.println("main 线程中有多少个子线程组: "+listGroup.length+" 名字为:"+listGroup[0].getName());
        Thread[] listThread = new Thread[listGroup[0].activeCount()];
        listGroup[0].enumerate(listThread);
        System.out.println(listThread[0].getName());
    }
}

运行结果如图

线程组 Thread Group_第3张图片

本程序代码的结构是在 main 线程组中创建一个新线程组,然后在该新线程组中添加了线程,并取得相关信息。

3.线程组自动归属特性 

自动归属就是自动归到当前线程组中(在没有指定线程组的情况下)。

新建测试用例

package org.example.Group;

public class Run {
    public static void main(String[] args) {
        //方法 activeGroupCount() 取得当前线程组对象中的子线程组数量
        //方法 enumerate() 的作用是将线程组中的子线程组以复制的形式
        // 复制到 ThreadGroup[] 数组对象中
        System.out.println("A处线程:"+Thread.currentThread().getName()
                +" 所属的线程组名为:"
                +Thread.currentThread().getThreadGroup().getName()
                +" "+" 中有线程组数量:"
                +Thread.currentThread().getThreadGroup().activeGroupCount());
        ThreadGroup group = new ThreadGroup("新的组"); // 自动加入到 main 组中
        System.out.println("B处线程:"+Thread.currentThread().getName()
                +" 所属的线程组名为:"
                +Thread.currentThread().getThreadGroup().getName()
                +" "+" 中有线程组数量:"
                +Thread.currentThread().getThreadGroup().activeGroupCount());
        ThreadGroup[] threadGroups = new ThreadGroup[Thread.currentThread().getThreadGroup()
                .activeGroupCount()];
        Thread.currentThread().getThreadGroup().enumerate(threadGroups);
        for (int i = 0; i < threadGroups.length; i++) {
            System.out.println("第一个线程组名称为:"+threadGroups[i].getName());
        }
    }
}

运行结果如图:

线程组 Thread Group_第4张图片

本实验要证明的是,在实例化 1 个 ThreadGroup 线程组 x 时,如果不指定所属的线程组,线程组 x 会自动归到当前线程对象所属的线程组中,也就是隐式地在线程组中添加了一个子线程组,所以在控制台打印的线程组数量值由 0 变成了 1 。

线程也具有这种特性,即自动归属到创建该线程的线程所在的组中。 

package org.example.Group;

public class Thread_ThreadGroup {
    static class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println("当前线程:"+Thread.currentThread().getName()
                    +"   所在的线程组: "+Thread.currentThread().getThreadGroup().getName());
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setName("myThread");
        myThread.start();
    }
}
public class Thread_ThreadGroup {

    static class Task implements Runnable{
        @Override
        public void run() {
            System.out.println("当前线程:"+Thread.currentThread().getName()
                    +"   所在的线程组: "+Thread.currentThread().getThreadGroup().getName());
            Thread thread = new Thread(() -> System.out.println("当前线程:"+Thread.currentThread().getName()
                    +"   所在的线程组: "+Thread.currentThread().getThreadGroup().getName()));
            thread.start();
        }
    }

    public static void main(String[] args) {
        Task task = new Task();
        ThreadGroup group = new ThreadGroup("子线程组");
        Thread myThread = new Thread(group,task);
        myThread.setName("myThread");
        myThread.start();
    }
}

运行结果如图

线程组 Thread Group_第5张图片    

4.获取根线程组 

JVM 的根线程组是 system,下面来进行测试。 

public class RunT {
    public static void main(String[] args) {
        System.out.println("线程: " + Thread.currentThread().getName()
                + "所在的线程组名称为:"
                + Thread.currentThread().getThreadGroup().getName());
        System.out.println("main 线程所在的线程组的父线程组的名称是:"
                + Thread.currentThread().getThreadGroup()
                .getParent().getName());
        System.out.println("main 线程所在的线程组的父线程组的父线程组的名称是:"
                + Thread.currentThread().getThreadGroup()
                .getParent()
                .getParent().getName());
    }
}

运行结果如图

线程组 Thread Group_第6张图片

证明 JVM 的根线程组就是 system,再取父线程组则出现空异常。 

5.线程组内加线程组  

本实验显示地在一个线程组中添加了一个子线程组

创建测试用例

package org.example.Group;

public class RunF {
    public static void main(String[] args) {
        System.out.println("线程组名称:"
                + Thread.currentThread().getThreadGroup().getName());
        System.out.println("线程组种活动的线程数量:"
                + Thread.currentThread().getThreadGroup().activeCount());
        System.out.println("线程组中线程组的数量 - 加之前:"
                + Thread.currentThread().getThreadGroup().activeGroupCount());
        ThreadGroup newGroup = new ThreadGroup(Thread.currentThread()
                .getThreadGroup(), "newGroup");
        System.out.println("线程组中线程组的数量 - 加之后:"
                + Thread.currentThread().getThreadGroup().activeGroupCount());
        System.out.println("父线程组名称:"
                + Thread.currentThread().getThreadGroup().getParent()
                .getName() + "线程组中活跃线程组:");
    }
}

运行结果如图

线程组 Thread Group_第7张图片

6.组内的线程批量停止 

使用线程组 ThreadGroup 的优点是可以批量处理本组内线程对象,比如可以批量中断组中的线程。 

新建测试用例

package org.example.Group;

public class groupInnerStop {
    static class MyThread extends Thread {
        public MyThread(ThreadGroup group, String name) {
            super(group, name);
        }

        @Override
        public void run() {
            System.out.println("ThreadName=" + Thread.currentThread().getName()
                    + "准备开始死循环");
            while (!this.isInterrupted()) {
            }
            System.out.println("ThreadName=" + Thread.currentThread().getName()
                    + "结束:)");
        }
    }

    public static void main(String[] args) {
        try{
            ThreadGroup group = new ThreadGroup("我的线程组");
            for (int i = 0; i < 5; i++) {
                MyThread thread = new MyThread(group,"线程 "+(i+1));
                thread.start();
            }
            Thread.sleep(5000);
            group.interrupt();
            System.out.println("调用了 interrupt() 方法");
        } catch (InterruptedException e) {
            System.out.println("停了停了!");
            e.printStackTrace();
        }
    }

}

运行结果如图

线程组 Thread Group_第8张图片

通过将线程归属到线程组,我们可以实现当调用线程组 ThreadGroup() 的 interrupt() 方法时中断该组中所有正在运行的线程。 

7.递归取得与非递归取得组内对象 

使用类 ThreadGroup 的 activeGroupCount() 方法取得子孙组的数量。然后根据数量创建 ThreadGroup[] 数组,最后通过 enumerate(ThreadGroup[] list, boolean recurse) 方法决定递归或者非递归将线程组对象复制(传引用)到该线程组数组中。

package org.example.Group;

public class groupRecurseTest {
    public static void main(String[] args) {
        ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
        ThreadGroup groupA = new ThreadGroup(mainGroup, "A");
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("runMethod!");
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        ThreadGroup groupB = new ThreadGroup(groupA, "B");
        //分配空间 但不一定全部用完
        ThreadGroup[] listGroup1 = new ThreadGroup[Thread.currentThread()
                .getThreadGroup().activeGroupCount()];
        //传入 true 时递归取得子线程组及子孙组
        Thread.currentThread().getThreadGroup().enumerate(listGroup1, true);
        for (int i = 0; i < listGroup1.length; i++) {
            if (listGroup1[i] != null) {
                System.out.println(listGroup1[i].getName());
            }
        }
        System.out.println("----------------------------");
        ThreadGroup[] listGroup2 = new ThreadGroup[Thread.currentThread()
                .getThreadGroup().activeGroupCount()];
        Thread.currentThread().getThreadGroup().enumerate(listGroup2, false);
        for (int i = 0; i < listGroup2.length; i++) {
            if (listGroup2[i] != null) {
                System.out.println(listGroup2[i].getName());
            }
        }

    }
}

程序运行结果如图

线程组 Thread Group_第9张图片  

同时需要值得注意的是,如果不传入是否递归的参数,也就是使用 enumerate(Thread[] list) 方法默认情况下是会递归的,详情可见 JDK 源码。默认传入的参数为 true

    public int enumerate(ThreadGroup list[]) {
        checkAccess();
        return enumerate(list, 0, true);
    }

enumerate(ThreadGroup[] list, boolean recurse) 的源码,两个方法最终都会调用 private int enumerate(ThreadGroup list[], int n, boolean recurse) 方法。该方法私有,是 ThreadGroup 类的内部实现,原理就是根据传入的 recurse 参数使用递归算法进行线程组的遍历。

    public int enumerate(ThreadGroup list[], boolean recurse) {
        checkAccess();
        return enumerate(list, 0, recurse);
    }

无论是否使用 recurse 参数进行方法的选择,都会使用  checkAccess() 方法进行检查,简单了解一下该方法,该方法用于确定当前线程是否有权限修改当前线程组。如果存在安全管理器,会调用其 checkAccess方法,以当前线程组作为参数。如果未获得访问权限,则会抛出SecurityException异常。


public final void checkAccess() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkAccess(this);
        }
    }
//此方法是上面方法的最后一步的调用
public void checkAccess(ThreadGroup g) {
        if (g == null) {
            throw new NullPointerException("thread group can't be null");
        }
        if (g == rootGroup) {
            checkPermission(SecurityConstants.MODIFY_THREADGROUP_PERMISSION);
        } else {
            // just return
        }

8. Thread.activeCount() 方法的使用 

public static int activeCount() 方法的作用是返回当前线程所在的线程组中活动线程的数目。 

    public static int activeCount() {
        return currentThread().getThreadGroup().activeCount();
    }

创建测试用例

package org.example.Group;

public class RunC {
    public static void main(String[] args) {
        System.out.println(Thread.activeCount());
    }
}

运行结果如图

线程组 Thread Group_第10张图片

可见当前线程所在的线程组中有 2活动线程,一般应为 1 个,这里可能会因为不同的 IDE 造成的结果不同,可能由集成的 IDE 创建一个用于监听热加载的线程、用于处理垃圾回收的线程,等等。 

9. Thread.enumerate(Thread tarray[]) 方法的使用

public static int enumerate(Thread tarray[]) 方法的作用是将当前线程所在的线程组及其子组中每一个活动线程复制到指定的数组中。该方法只调用当前线程所在的线程所在的线程组的 enumerate 方法,且带有数组参数,

    public static int enumerate(Thread tarray[]) {
        return currentThread().getThreadGroup().enumerate(tarray);
    }

创建测试用例

public class RunE {
    public static void main(String[] args) {
        //这里是通过实例引用访问 static 成员 'java.lang.Thread.activeCount()'
        //正常 Thread.activeCount() 就可以了,为了更直观的感受
        Thread[] threads = new Thread[Thread.currentThread().activeCount()];
        Thread.enumerate(threads);
        for (int i = 0; i < threads.length; i++) {
            System.out.println(threads[i].getName());
        }
    }
}

 运行结果如图

线程组 Thread Group_第11张图片

同时,也证明了上一个实验中的结论:即 2 个活动线程中含有一个监控线程 Monitor Ctrl-Break ,当然这个线程是 IntelliJ IDEA 特有的用来监听旁边停止按钮所产生的中断信号的线程。


10.补充  

 补充关于 enumerate()

int

enumerate(Thread[] list)

将此线程组及其子组中的每个活动线程复制到指定的数组中。

int

enumerate(Thread[] list, boolean recurse)

将此线程组中的每个活动线程复制到指定的数组中。

int

enumerate(Thread[] list, boolean recurse)

复制到该线程组及其子组中每个活动子组的指定数组引用。

int

enumerate(ThreadGroup[] list, boolean recurse)

复制到该线程组中每个活动子组的指定数组引用。

补充关于 activeGroupCount() ;

activeGroupCount() 是 Java ThreadGroup 类的一个方法。此方法用于估算调用该方法的线程组中活动线程组的数量。"活动线程组"是指那些未被销毁且不是守护线程组的线程组。

要注意的是,activeGroupCount() 方法返回的值是一个估算值,因为在一个线程组的子线程组中的线程可能在计算过程中开始或结束。你不能依靠这个值进行精确的操作,但它对于监控和调试线程行为很有价值。


总结

线程组是 Java 中的一个概念,用于管理线程集合。一个线程组可以包含线程,并且可以是另一个线程组的子项。线程组形成的是一个树状结构,除了系统线程组(根线程组)之外,每个线程组都有一个父线程组。线程可以访问一些有关自己所属线程组的信息,比如线程组的名称,以及线程组中活动线程的数目等。

加油!!!!!

你可能感兴趣的:(多线程,java,多线程)