大飞老师带你看线程-线程组

阅读次数:2    时间:2018-08-24     作者:王一飞
概念

线程组表示一个线程的集合。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。

一般而言,会将一些功能相同,或者类似的线程放置到同一个线程组中,方便统一管理。

结构

java中使用ThreadGroup类描述线程,其体系结果有点类似树状结构。
线程组结构

上图可以看出:
1>线程组可以拥有线程,也可以拥有子线程组。而线程组级别最高是系统线程组,由jvm维护。
2>系统线程组有一个比较特殊的子线程组,main线程组,代码执行的main线程就属于这个线程组。
另外,需要注意:
1>新线程创建时如果没有指定线程组,默认属于当前线程所在的线程组。
2>新线程创建时如果指定了线程组,其线程组的父线程组就是当前线程所在的线程组

public class App {
    public static void main(String[] args) {
        //当前main线程所在的线程组名字:main
        System.out.println(Thread.currentThread().getThreadGroup().getName());
        //当前main线程所在的线程组父线程组名字:system
        System.out.println(Thread.currentThread().getThreadGroup().getParent().getName());

        //自定义一个线程
        ThreadGroup threadGroup = new ThreadGroup("tg1");

        //创建线程, 指定线程组为tg1
        Thread thread = new Thread(threadGroup, new Runnable() {
            public void run() {
                Thread t = new Thread(new Runnable() {
                    public void run() {
                        //dosomething
                    }
                });
                //新线程t没有指定线程组,默认为thread的线程组
                //打印新创建的线程t的线程组:tg1
                System.out.println(t.getThreadGroup().getName());
            }
        });

        //新线程thread创建时指定的线程组,线程组名字:tg1
        System.out.println(thread.getThreadGroup().getName());
        //新线程thread创建时指定的线程组,那父线程组默认是当前main线程的线程组:main
        System.out.println(thread.getThreadGroup().getParent().getName());
    }
}
基本api

ThreadGroup
构造器:

//构建一个指定名字的线程组
ThreadGroup(String name)
//构建一个指定名字跟父线程组的线程组 
ThreadGroup(ThreadGroup parent, String name)

方法:

//获取此线程组活动线程个数,是一个估值,不精确
int activeCount()  
//获取此线程获取的子线程组个数
int activeGroupCount()  
//销毁此线程组中所有的线程与子线程组
void destroy()  
//获取此线程组的名字
String getName()  
//获取此线程组的父线程组
ThreadGroup getParent()  
//把此线程组及其子组中的所有活动线程复制到指定数组中。
enumerate(Thread[] list) 
//把对此线程组中的所有活动子组的引用复制到指定数组中。
enumerate(ThreadGroup[] list) 
//中断此线程组中的所有线程
void interrupt()  
//将有关此线程组的信息打印到标准输出。
void list()  
//当此线程组中的线程因为一个未捕获的异常而停止,并且线程没有安装特定 
//Thread.UncaughtExceptionHandler 时,由 JVM调用此方法。
void uncaughtException(Thread t, Throwable e)

Thread线程:

//参数1:线程组
Thread(ThreadGroup group, Runnable target) 
Thread(ThreadGroup group, Runnable target, String name)
public class AppApi {
    public static void main(String[] args) {

        //创建指定名字的线程组
        ThreadGroup tg = new ThreadGroup("tg1");
        //获取线程组名称
        System.out.println(tg.getName());
        //获取线程组父线程
        System.out.println(tg.getParent());
        for (int i = 0; i < 5; i++){
            new Thread(tg, new Runnable() {
                public void run() {
                    try {
                        //睡1s
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        //该线程组活动的线程数:
        System.out.println(tg.activeCount());
        //该线程组活动的子线程数:
        System.out.println(tg.activeGroupCount());
        tg.list();
    }
}

结果:

tg1
java.lang.ThreadGroup[name=main,maxpri=10]
5
0
java.lang.ThreadGroup[name=tg1,maxpri=10]
    Thread[Thread-0,5,tg1]
    Thread[Thread-1,5,tg1]
    Thread[Thread-2,5,tg1]
    Thread[Thread-3,5,tg1]
    Thread[Thread-4,5,tg1]
作用

1>线程统一管理:统一停止
需求:分段计算1到21亿的和

//分段计算线程
public class SumTask implements  Runnable{
    //分段计算值的和
    private long sum = 0;
    //开始位置:可以取等
    private long begin;
    //结束位置:不可以取等
    private long end;

    private NumCount numCount;

    public SumTask(NumCount numCount, long begin, long end){
        this.numCount = numCount;
        this.begin = begin;
        this.end = end;
    }
    public void run() {
        try {
            //计算规则:左闭右开
            if(!Thread.currentThread().isInterrupted()){
                for(long i = begin; i < end; i++){
                    //如果线程组停止,直接跳出,此次计算和不算入总数
                        sum += i;
                }
            }
            //将计算结果归总到中结果中
            this.numCount.sum(sum);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public long getSum() {
        return sum;
    }
}
//需求:分段计算1到21亿的和
public class NumCount {

    //21亿
    private long total = 2100000000L;
    //分100个线程计算
    private int threadSize = 100;

    //每个线程计算长度
    private long interval = total / threadSize;
    //总和
    private volatile long sum = 0;

    //开始计算
    public void count(ThreadGroup tg){
        //启动100个线程计算
        long begin = 0;
        long end = 0;
        for (int i = 0; i < 100; i++){
            begin = interval * i;
            end = begin + interval + 1;  //计算规则:左闭右开,所以加一
            new Thread(tg, new SumTask(this, begin, end), "SumTask_" + i).start();
        }
    }

    public synchronized void sum(long value){
        System.out.println(Thread.currentThread().getName() + ":" + value);
        this.sum += value;
    }

    public long getSum() {
        return sum;
    }

    //等待所有线程完毕
    public void waitFinish(ThreadGroup tg){
        while (tg.activeCount() > 0){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class App {
    public static void main(String[] args) {

        //创建指定名字的线程组
        ThreadGroup tg = new ThreadGroup("tg1");

        NumCount numCount = new NumCount();
        //启动线程执行
        numCount.count(tg);

        //手工等待计算完
        numCount.waitFinish(tg);
        //强制性一次停止
        //tg.interrupt();
        System.out.println(numCount.getSum()); //2205000105000000000

    }
}

注意:使用2种方式实现分段线程的终止
方式1:使用手动方法numCount.waitFinish(tg),死循环直到线程组中活动线程为0
方式2:调用线程组的tg.interrupt()方法,直接结束分段线程

2>线程组统一异常处理
线程组提供一个uncaughtException 方法实现统一异常处理。当组内线程执行报错时, 会字典调用uncaughtException方法,传入线程对象以及异常信息
改造一下分段计算的案例:

public class App4 {
    public static void main(String[] args) {

        //创建指定名字的线程组
        //ThreadGroup tg = new ThreadGroup("tg1");

        final NumCount numCount = new NumCount();
        //自定义一个线程组对象,重写uncaughtException
        ThreadGroup tg = new ThreadGroup("tg1"){

            //需要重写uncaughtException方法, 表示组线程报错后统一调用该方法
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(t.getName() + ": " + e.getMessage());
                //再执行额外的补救
                //因为使用是接口方法启动线程无法获取到线程的target接口
                //只能通过反射间接获取, 最佳的操作是SumTask继承Thread类
                try {
                    //通过反射或者Thread中拥有的runable对象
                    Class clz = t.getClass();
                    Field target = clz.getDeclaredField("target");
                    target.setAccessible(true);
                    SumTask task = (SumTask) target.get(t);

                    long sum = 0;
                    //手动补救:
                    for(long i = task.getBegin(); i < task.getEnd(); i++){
                        sum += i;
                    }
                    numCount.sum(sum);

                } catch (NoSuchFieldException e1) {
                    e1.printStackTrace();
                } catch (IllegalAccessException e1) {
                    e1.printStackTrace();
                }
            }
        };
        //启动线程执行
        numCount.count(tg);

        //手工等待计算完
        numCount.waitFinish(tg);
        //强制性一次停止
        //tg.interrupt();
        System.out.println(numCount.getSum()); //2205000105000000000
    }
}
//分段计算线程
public class SumTask implements  Runnable{
    //分段计算值的和
    private long sum = 0;
    //开始位置:可以取等
    private long begin;
    //结束位置:不可以取等
    private long end;

    private NumCount numCount;

    public SumTask(NumCount numCount, long begin, long end){
        this.numCount = numCount;
        this.begin = begin;
        this.end = end;
    }
    public void run() {
        //当运行到某一个位置时, 模拟丢一个异常
        if(begin == 1659000000){
            throw new RuntimeException("在" + begin +"到"+ end + "数字段计算报错");
        }
        try {
            //计算规则:左闭右开
            if(!Thread.currentThread().isInterrupted()){
                for(long i = begin; i < end; i++){
                    //如果线程组停止,直接跳出,此次计算和不算入总数
                        sum += i;
                }
            }
            //将计算结果归总到中结果中
            this.numCount.sum(sum);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public long getSum() {
        return sum;
    }

    public long getBegin() {
        return begin;
    }

    public long getEnd() {
        return end;
    }
}

修改了run方法, 模拟异常
运行结果:
分段计算结果
通过补救之后,计算结果一样没错
最后总和

结语

一般而言, 开发中更加推荐使用线程池。它俩有什么区别,下篇再继续讲解。本篇到此结束。