概念
线程组表示一个线程的集合。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。
一般而言,会将一些功能相同,或者类似的线程放置到同一个线程组中,方便统一管理。
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());
}
}
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方法, 模拟异常
运行结果:
通过补救之后,计算结果一样没错
一般而言, 开发中更加推荐使用线程池。它俩有什么区别,下篇再继续讲解。本篇到此结束。