线程组与线程池
以前接触学习线程组时,并没有太深入去了解其中的东西,原因是当初和同学讨论赛车游戏中,倒计时出发场景,是怎么实现多个线程同时启动的?所以就转去学习线程池了。不过后来发现其实是可以用CountDownLatch或者CyclicBarrier - -、。现在重新拾起了线程组,决定写篇日志,总结一些线程组相关。
创建线程池的目的,相信大家都很熟悉,就是为了解决许多的线程频繁创建和销毁,需要花费大量时间而导致效率不佳和资源耗尽利用问题。方法是在线程池中,始终存在一些活跃的线程,如果需要使用线程,就从线程池中取出,用完后把线程放回到线程池里,在需要时再唤醒。
创建线程组的目的,更多的是为了管理线程,以及线程安全问题。管理线程用分类的思想,通常将一些功能相同或相似的线程放到一个线程组中一起管理,例如数据库连接线程和数据库操作线程等。至于安全问题,保证线程数据安全的做法是,对于在同一个线程组内的线程可以互相修改其他线程的数据,而处于不同线程组中的线程,不能修改互相的数据。
这么看来,线程组和线程池的有一共同过作用都是可以用来管理线程,不同的地方有,线程池中不能放入线程池,也就是不能嵌套线程池,但是在线程组中可以嵌套放入线程组,这样一来,线程组不光可以统一管理线程,也可以管理放在它里面的线程组。接下来就总结一些关于线程组相关的特性,看看它与线程池还有哪一些区别。
ThreadGroup源码
回想之前的自定义线程池,想要知道线程池里有些什么属性,是通过看它的构造方法public ThreadPoolExecutor();里面有指定线程池中线程的数量、最大线程数、空闲线程存活时间,任务队列以及拒绝服务等属性,我们想要自定义线程池,也是通过修改构造方法中的参数来实现。同样,想了解线程组里面有些什么属性,先来看看它的构造方法中都有些什么。在Java中用ThreadGroup类来描述,它有两个public的构造方法:
public ThreadGroup(String name) {
this(Thread.currentThread().getThreadGroup(), name);
}
public ThreadGroup(ThreadGroup parent, String name) {
this(checkParentAccess(parent), parent, name);
}
这两个构造方法用来给我们初始化线程组用,从中看不出太多的属性,只有设置线程组名和它的父线程组。ThreadGroup类中还有一个私有的构造方法,它里面才包含了线程组中的一些关键属性:
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
private final ThreadGroup parent;
String name;
int maxPriority;
boolean destoryed;
boolean daemon;
boolean vmAllowSuspension;
int nUnstartedThreads = 0;
int nthread;
Thread threads[];
int ngroups;
ThreadGroup groups[];
private ThreadGroup(void unused, ThreadGroup parent, String name) {
this.name = name;
this.maxPriority = parent.maxPriority;
this.daemon = parent.daemon;
this.vmAllowSuspension = parent.vmAllowSuspension;
this.parent = parent;
parent.add(this);
}
}
可以看到,在一个线程组中,除了有线程组名,父线程组外,还有线程组的最大允许优先级,是否为守护线程,以及线程组的子线程组。线程组中保存线程使用了数组threads,保存子线程组也一样是数组形式groups。这也证明了,线程组中既可以存放线程实例对象,也可以存放线程组。我们可以写些简单的代码,看看这些线程组中的关联关系。
线程组的默认归属
在检验测试线程组的关联关系前,先了解一个重要的特性,就是线程组的“默认归属”特性。什么是默认归属?指的是新建线程组时,如果没有指定线程组的父线程组,那么该线程组会默认自动归属到当前线程的线程组中。来看个例子:
public class ThreadGroupTest {
public static void main(String[] args) {
ThreadGroup threadGroup = new ThreadGroup("MyThreadGroup");
System.out.println("线程组" + threadGroup.getName() + " 的父线程组为 ");
System.out.println(threadGroup.getParent() + "\r\n");
System.out.println("主线程的线程组名: " + Thread.currentThread().getThreadGroup().getName());
System.out.println("主线程组中的子线程组数: " + Thread.currentThread().getThreadGroup().activeGroupCount());
// 复制取得主线程组
ThreadGroup[] listThreadGroup = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
Thread.currentThread().getThreadGroup().enumerate(listThreadGroup);
System.out.println("主线程组中的子线程组名: " + listThreadGroup[0].getName() + "\r\n");
}
我们先新建一个线程组MyThreadGroup,没有给它指定父线程组,这样的初始化出来的线程组,它是不是就没有父线程组呢?下面我们可以测试一下,第4行输出该线程组的父线程组:
从输出结果可以看到,即便我们新建的线程组MyThreadGroup没有指定其父线程组,但它还是有一个名为“main”的父线程组。因为对于一个线程组的创建,他都一定会分配一个默认的父线程组,如果在创建线程组时不指定其父线程组,那么就会把当前线程所在的线程组作为该线程组的父线程组。
在上面的例子中,我们只有一个主线程,在主线程里我们创建一个线程组MyThreadGroup,那么它的父线程组便默认归属为主线程所在的线程组(假如没有显式地指定其父线程组)。在代码中我们通过获得当前线程(也就是主线程)的线程组,把它复制到一个线程组数组中,然后输出里面的子线程组名,结果为“MyThreadGroup”,也就证明了这个线程组被默认归属到了其所在线程的线程组中。不光线程组会有默认归属的特性,线程也有,每一个线程都必须属于某一个线程组,我们同样可以写个简单的例子测试一下:
public class ThreadGroupTest {
public static void main(String[] args) {
ThreadGroup threadGroup = new ThreadGroup("MyThreadGroup");
System.out.println("线程组" + threadGroup.getName() + " 的父线程组为 ");
System.out.println(threadGroup.getParent() + "\r\n");
System.out.println("主线程的线程组名: " + Thread.currentThread().getThreadGroup().getName());
System.out.println("主线程组中的子线程组数: " + Thread.currentThread().getThreadGroup().activeGroupCount());
Thread T1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " running....");
}
});
T1.setName("testThread");
T1.start();
// 复制取得主线程组
ThreadGroup[] listThreadGroup = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
Thread.currentThread().getThreadGroup().enumerate(listThreadGroup);
System.out.println("主线程组中的子线程组名: " + listThreadGroup[0].getName() + "\r\n");
//复制取得主线程组中的线程集合
Thread[] listThread = new Thread[Thread.currentThread().getThreadGroup().activeCount()];
Thread.currentThread().getThreadGroup().enumerate(listThread);
System.out.println("主线程组中的线程数: " + listThread.length);
System.out.println("主线程组中的线程有: ");
for(int i=0; i<listThread.length; i++) {
System.out.println(listThread[i].getName());
}
}
在代码中我们新建一个线程T1,里面只做一句输出running,并给这个线程设置一个名字“testThread”,但不给它指定所属线程组。然后第26行我们创建一个线程数组,复制得到主线程组中的线程,最后遍历输出线程数组listThread中的所有线程名,看看里面都有哪些线程?
可以看到,在主线程组中,包含的线程数量有两个,除了主线程main外,还有我们刚刚新建的线程,它也被默认归属到了主线程中。
一级关联:线程组中的线程
对于一些有相似功能的线程,我们可以把它交由同一个线程组管理,例如数据库查询,增删查改操作,像这样在一个父进程组中包含若干线程的组织管理,就是一级关联。我们看个例子:
package threadgroupinstance;
public class PrimaryCorrelation implements Runnable {
@Override
public void run() {
try {
while(true) {
System.out.println(Thread.currentThread().getName() + "running....");
Thread.sleep(5000);
}
} catch(InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ThreadGroup threadGroup = new ThreadGroup("MyThreadGroup");
for(int i=0; i<3; i++) {
PrimaryCorrelation runnableInstance = new PrimaryCorrelation();
Thread thread = new Thread(threadGroup, runnableInstance);
thread.start();
}
System.out.println("线程组名:" + threadGroup.getName());
System.out.println("线程组中活跃的线程数:" + threadGroup.activeCount());
}
}
按照一级关联,把一些线程归属到一个线程组中。第17行我们创建一个线程组,之后for循环三次,新建线程并通过构造器初始化把线程放入到我们的线程组MyThreadGroup中,并让线程启动。最后第25,26行我们可以输出线程组中活跃的线程数,按道理会输出三个活跃的线程,这三个线程都会不停打印自己的名字:
嵌套关联:线程组中又有线程组
嵌套关联不复杂,就是在线程组中,嵌套有线程组,父线程组中有线程和子线程组,子线程组里也可以有线程和嵌套线程组。这里我说的不复杂,指的是构造起来不复杂,但如果不加控制地嵌套线程组,可能会导致结构变得十分复杂,就像广义表结构,或者树结构。下面来看个例子,构建一个嵌套关联的线程组:
package threadgroupinstance;
public class MultiCorrelation implements Runnable {
@Override
public void run() {
try {
while(true) {
System.out.println(Thread.currentThread().getName() + " start.");
Thread.sleep(5000);
}
} catch(InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 获得主线程所在的线程组
ThreadGroup mainThreadGroup = Thread.currentThread().getThreadGroup();
// 创建一个新的线程组,线程组threadGroup的父线程组为mainGroup
ThreadGroup threadGroup = new ThreadGroup(mainThreadGroup, "MyThreadGroup");
// 创建线程放入到嵌套线程组中
MultiCorrelation runnableInstance = new MultiCorrelation();
Thread threadInstance = new Thread(threadGroup, runnableInstance);
threadInstance.setName("MyThreadGroup - Thread1");
threadInstance.start();
ThreadGroup[] listThreadGroup = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
// 主线程的线程组复制到listThreadGroup中
Thread.currentThread().getThreadGroup().enumerate(listThreadGroup);
System.out.println("线程组mainThreadGroup中的子线程组数: " + listThreadGroup.length);
System.out.println("嵌套线程组名:" + listThreadGroup[0].getName());
Thread[] listThread = new Thread[listThreadGroup[0].activeCount()];
// 嵌套线程组中的活跃线程复制到listThread中
listThreadGroup[0].enumerate(listThread);
System.out.println("嵌套线程组中的活跃线程: " + listThread[0].getName());
}
}
按照思路,我们在一个线程组中,嵌套一个子线程组,然后在子线程组里运行线程。
批量中断线程
上面说到,无论是线程组还是线程池,都有一共同过作用:可以用来管理线程,下面来看个例子,用线程组来批量中断里面的所管理的线程:
package threadgroupinstance;
public class BatchInterruptedInstance implements Runnable {
@Override
public void run() {
try {
while(!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + " running.");
Thread.sleep(2000);
}
} catch(InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "end.");
e.printStackTrace();
}
}
public static void main(String[] args) {
ThreadGroup threadGroup = new ThreadGroup("MyThreadGroup");
try {
for(int i=0; i<4; i++) {
BatchInterruptedInstance instance = new BatchInterruptedInstance();
Thread T = new Thread(threadGroup, instance);
T.start();
}
Thread.sleep(4000);
// 中断线程组内所有线程
threadGroup.interrupt();
System.out.println("调用interrupt方法");
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
在代码中,我们创建四个线程,线程要做的事情就是只要没有被中断,就每隔2秒输出一下。把这四个线程都归到线程组“MyThreadGroup”中,第27行主线程休眠4秒,为了让前面的所有线程都先启动,然后调用interrupt()方法,批量中断线程组中的所有线程:
可以看到,调用完interrupt()方法后,四个线程都被中断,进入catch语句,输出了错误堆栈信息。
递归与非递归取出线程组
把线程提交给线程组管理,线程组提供两种取出组内线程的方法,递归方式和非递归方式。相信学习过二叉树递归遍历和非递归遍历的你应该会理解,因为在线程组中,可以嵌套线程组,嵌套线程组中可以放线程,也可以继续嵌套线程组,所以复杂的线程组结构其实就类似于树状结构,对于树状结构,我们自然可以进行递归取出线程组。下面来看看这两种方式的例子,首先是非递归:
package threadgroupinstance;
public class RecursiveScanning {
public static void main(String[] args) {
// 获得主线程组
ThreadGroup mainThreadGroup = Thread.currentThread().getThreadGroup();
ThreadGroup threadGroup1 = new ThreadGroup(mainThreadGroup, "threadGroup1");
ThreadGroup threadGroup2 = new ThreadGroup(mainThreadGroup, "threadGroup2");
ThreadGroup threadGroup3 = new ThreadGroup(threadGroup1, "threadGroup3");
ThreadGroup[] listThreadGroup = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
// 非递归取得主线程祖中的子线程组
Thread.currentThread().getThreadGroup().enumerate(listThreadGroup, false);
System.out.println("非递归取出子线程组");
for(int i = 0; i<listThreadGroup.length; i++) {
if(listThreadGroup[i] != null) {
System.out.println(listThreadGroup[i].getName());
}
}
}
}
首先我们取得主线程的线程组,然后新建三个线程组,把它们都放到主线程组中,其中线程组threadGroup1和threadGroup2都是主线程祖的子线程组,而threadGroup3是threadGroup1的子线程组。然后我们把主线程组复制出来到listThreadGroup中,调用enumerate()方法时,第二个参数为false,表示非递归取出组内的子线程组,按照非递归的方式,最后我们便只得到threadGroup1和threadGroup2:
但是如果我们用递归的方式取出子线程组,把enumerate()方法里的false改为true,那么我们会得到主线程祖内所有的子线程组:
package threadgroupinstance;
public class RecursiveScanning {
public static void main(String[] args) {
// 获得主线程组
ThreadGroup mainThreadGroup = Thread.currentThread().getThreadGroup();
ThreadGroup threadGroup1 = new ThreadGroup(mainThreadGroup, "threadGroup1");
ThreadGroup threadGroup2 = new ThreadGroup(mainThreadGroup, "threadGroup2");
ThreadGroup threadGroup3 = new ThreadGroup(threadGroup1, "threadGroup3");
ThreadGroup[] listThreadGroup = new ThreadGroup[Thread.currentThread().getThreadGroup().activeGroupCount()];
// 递归取得主线程祖中的子线程组
Thread.currentThread().getThreadGroup().enumerate(listThreadGroup, true);
System.out.println("递归取出子线程组");
for(int i = 0; i<listThreadGroup.length; i++) {
if(listThreadGroup[i] != null) {
System.out.println(listThreadGroup[i].getName());
}
}
}
}
完整代码已上传GitHub: