线程组 - 关联线程与管理

线程组与线程池

以前接触学习线程组时,并没有太深入去了解其中的东西,原因是当初和同学讨论赛车游戏中,倒计时出发场景,是怎么实现多个线程同时启动的?所以就转去学习线程池了。不过后来发现其实是可以用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:

https://github.com/justinzengtm/Java-Multithreading

https://gitee.com/justinzeng/multithreading

发布了97 篇原创文章 · 获赞 71 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/justinzengTM/article/details/99733359
今日推荐