Java线程基础-Thread ThreadGroup ThreadLocal ThreadGroupContext 之ThreadGroup (二)

这个系列的出现可以说就是因为ThreadGroup,相对于Thread、ThreadLocal可能部分Java程序员,对于ThreadGroup比较陌生,或者说知道是什么,但是不怎么用,接下来会从ThreadGroup的概念以及具体应用进行介绍,希望大家从中可以学到什么。

2. ThreadGroup

不要问为什么从2开始,因为上一章是1。

2.1 认识ThreadGroup

引用官方SDK的介绍如下:

线程组表示一个线程的集合。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。
允许线程访问有关自己的线程组的信息,但是不允许它访问有关其线程组的父线程组或其他任何线程组的信息

怎么形象地理解这个线程组?在上一章不知道有没有留意一个线程的创建其中一个步骤就是将线程添加到某个线程组,那么主线程可能也是有属于某个线程组的,因此我们还是从主线程入手。

循环输出一下父级线程组:

   public static void main(String[] args) throws Exception {
        //获取当前线程
       Thread currentThread =  Thread.currentThread();
       //获取当前线程所在的线程组
        ThreadGroup currentThreadGroup = currentThread.getThreadGroup();
        System.out.println("当前线程名称:"+currentThread.getName() );
        while (currentThreadGroup!=null){
            System.out.println("父线程组名称:"+currentThreadGroup.getName() );
            currentThreadGroup = currentThreadGroup.getParent();
        }
    }

执行后输出结果:
在这里插入图片描述
根据这个结果我们画个图表示一下关系,
大概是这样子
在这里插入图片描述
从上图可以看到线程与线程组的所属关系以及线程组与线程组之间的所属关系,而最爸爸的线程组就是system线程组,为什么这样说呢,我们进行实验如下:

        //创建一个线程组threadgroup 1
        ThreadGroup tg1 = new ThreadGroup ("threadgroup 1");
        //创建threadgroup 1下的一个线程组
        Thread t1 = new Thread (tg1, "thread 1");
        currentThreadGroup = t1.getThreadGroup();
        while (currentThreadGroup!=null){
            System.out.println("父线程组名称:"+currentThreadGroup.getName() );
            currentThreadGroup = currentThreadGroup.getParent();
        }

控制台输出结果如下:
在这里插入图片描述
我们再重新画一下图:
在这里插入图片描述
对于为什么出现这个结果,我们直接看ThreadGroup对象创建过程,他实际上就是在默认情况下拿当前线程所在线程组创建,刚刚我们在main线程中创建这个线程组threadgroup 1,因此它是属于main线程组的子线程组。
在这里插入图片描述
集合第一章对线程得学习,因此我们可以总结得:

1.JVM创建的system线程组是用来处理JVM的系统任务的线程组,例如对象的销毁等。
2.system线程组的直接子线程组是main线程组,这个线程组至少包含一个main线程,用于执行main方法。
3.main线程组的子线程组就是应用程序创建的线程组。

2.2 ThreadGroup的作用

在Java的程序定义里面,把每个线程都归属一个线程组,又在最大线程组system下不断创建子线程组,这样的树形结构有什么意义?

接下来我们从ThreadGroup本身的属性进行入手
在这里插入图片描述

可以看到它包含的属性主要是父线程组、名称、最大优先级、是否被销毁、是否启动、 是否供VM使用、未开始的线程数量、线程数量、线程数组、线程组数量、线程组数组。

查看它的构造方法,如何初始化这些属性
在这里插入图片描述
除了自定义的name和父线程组,线程组的属性都是follow它的父线程组。

ThreadGroup的方法有:
在这里插入图片描述
具体介绍可以去sdk进行查询,这里不作详细介绍。
在上面列出的方法里面,经过了解,线程组具备能力就是控制整个线程组下的所有线程的状态,而由于ThreadGroup本身并非线程安全,线程组ThreadGroup对象中的stop,resume,suspend会导致安全问题,主要是死锁问题,已经被官方废弃,剩余与线程生命周期相关的就是

   destroy()           销毁此线程组。只有在当前线程组线程数量=0的时候有效
   interrupt()         中断此线程组中的所有线程。

2.2.1 添加线程

我们可以从Thread创建的源码里面,直接跳到ThreadGroup.add(),线程的创建在进入jvm创建系统子线程前,先将线程放进线程组

    void add(Thread t) {
        synchronized (this) {
            if (destroyed) {
                throw new IllegalThreadStateException();
            }
            if (threads == null) {
                threads = new Thread[4];
            } else if (nthreads == threads.length) {
                threads = Arrays.copyOf(threads, nthreads * 2);
            }
            threads[nthreads] = t;

            // This is done last so it doesn't matter in case the
            // thread is killed
            nthreads++;

            // The thread is now a fully fledged member of the group, even
            // though it may, or may not, have been started yet. It will prevent
            // the group from being destroyed so the unstarted Threads count is
            // decremented.
            nUnstartedThreads--;
        }
    }

这段add方法的实现,简单来说就是,首先判断当前线程组是否被销毁,假如否,抛异常,假如是就将创建的新线程放进线程组对象里面,线程组初始大小为4,线程组的增长是按2倍增长,进行线程组线程数量计算。

2.2.2 中断线程

ThreadGroup具有中断该组下的线程的作用,具体实现如下:

  // 创建2个MyThread属于tg1
        MyThread mt = new MyThread(tg1,"A");
        mt.start();
        mt =  new MyThread(tg1,"B");
        mt.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断线程组
        System.out.println("中断tg1");
        tg1.interrupt();

   //启动以后进入等待,直到被interrupt的线程
    static class MyThread extends Thread {
        public MyThread(ThreadGroup tg,String name){
            super(tg,name);
        }
        public void run() {
            synchronized ("A") {
                System.out.println(getName() + " 等待中");
                try {
                    "A".wait();
                } catch (InterruptedException e) {
                    System.out.println(getName() + " 中断");
                }
                System.out.println(getName() + " 停止");
            }
        }
    }

控制台输出结果:
在这里插入图片描述

ThreadGroup的源码实现如下:

  public final void interrupt() {
        int ngroupsSnapshot;
        ThreadGroup[] groupsSnapshot;
        synchronized (this) {
            checkAccess();
            for (int i = 0 ; i < nthreads ; i++) {
                threads[i].interrupt();
            }
            ngroupsSnapshot = ngroups;
            if (groups != null) {
                groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot);
            } else {
                groupsSnapshot = null;
            }
        }
        for (int i = 0 ; i < ngroupsSnapshot ; i++) {
            groupsSnapshot[i].interrupt();
        }
    }

核心中断线程的是, threads[i].interrupt();这一句,实际最后是通过调用native interrput0(),调用jvm中断系统线程。然后他把线程组对象复制到新的的线程组快照对象里面。

2.2.3 作用总结

从这里来看,对于线程来说,线程组并没有比较能用上的作用,一般非必要的情况下,线程的生命周期管理还是交给线程自己主导,并不使用线程组去控制。

2.3 线程组与线程池的区别

线程组是线程的爸爸,每一个线程一定是属于某个线程组的,所有线程组都是来自于system线程组。
线程池是没有血缘关系的,线程池是为了最大化利用计算机能力而出现,由于我们cpu调度能力有限,不能无限创建线程,无限并行任务,因此出现线程池, 线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

发布了32 篇原创文章 · 获赞 26 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_28540443/article/details/104596118