线程组(ThreadGroup
)表示一个线程的集合。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组(system)外,每个线程组都有一个父线程组。 允许线程访问有关自己的线程组的信息,但是不允许它访问有关其线程组的父线程组或其他任何线程组的信息。
每一个线程产生时,都会被归入某个线程组,视线程是在哪个线程组中产生而定。如果没有指定,则归入产生该子线程的线程的线程组中。
ThreadGroup类正如其名,可以统一管理整个线程组中的线程,ThreadGroup中的某些方法,可以对所有的线程产生作用,例如interrupt
()方法可以interrupt线程组中所有的线程。
当一个应用启动时,默认会创建两个线程组,system
线程组和main
线程组。其中system线程组是main线程组的父线程组。
system线程组是JVM工作过程中,可能需要一些线程,例如垃圾回收线程,信号转发线程。
main线程组,主要用户创建的线程,如果开发者在创建线程的时候,没有指定线程组,那么就会归于main线程组。
public class ThreadGroupDemo { public static void main(String[] args) { Thread t1=new Thread(){ @Override public void run() { try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } }; t1.start(); ThreadGroup threadGroup = t1.getThreadGroup(); System.out.println(threadGroup); ThreadGroup systemThreadGroup = threadGroup.getParent(); System.out.println(systemThreadGroup); systemThreadGroup.list();//列出线程组树形结构,只会打印出已经start的线程信息 } }
上述代码运行后,会输出树状线程组结构:
java.lang.ThreadGroup[name=main,maxpri=10] java.lang.ThreadGroup[name=system,maxpri=10] Thread[Reference Handler,10,system] Thread[Finalizer,8,system] Thread[Signal Dispatcher,9,system] Thread[Attach Listener,5,system] java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main] Thread[Monitor Ctrl-Break,5,main] Thread[Thread-0,5,main]
ThreadGroup在实际开发中使用的较少。在<<THE JavaTM Programming Language, Fourth Edition>>一书的14.11节中,提到了为什么要引入线程组这个概念:
相关内容截图如下所示:
可以看到,引入ThreadGroup主要是2个原因:管理和安全。
ThreadGroup提供了多个方法方面我们对线程进行统一的管理。您可以使用以下方式来产生线程组,也可以自行指定线程组,线程一旦归入某个组,就无法更换组。
public Thread(ThreadGroup group, String name)
下面是一个简单的案例:
通过ThreadGroup的activeCount
获取当前活跃的线程
通过ThreadGroup的interrupt
方法中断所有的线程
public class ThreadGroupDemo2 { static class SleepTask implements Runnable { @Override public void run() { try { TimeUnit.HOURS.sleep(1);// 休眠1小时 } catch (InterruptedException e) { //ignore } } } public static void main(String[] args) { ThreadGroup threadGroup = new ThreadGroup("group"); Thread thread1 = new Thread(threadGroup, new SleepTask(), "thread1"); Thread thread2 = new Thread(threadGroup, new SleepTask(), "thread2"); //activeCount方法用于返回当前活跃的线程,因为刚创建时2个线程都没有启动因此返回0 assert threadGroup.activeCount() == 0; thread1.start(); thread2.start(); //线程组中的2个线程都启动了,active为2 assert threadGroup.activeCount() == 2; threadGroup.interrupt(); //中断之后,active为0 assert threadGroup.activeCount() == 0; } }
ThreadGroup还有有一些其他方法,实际开发中基本也很少使用。唯一值得一说的是ThreadGroup实现了Thread.UncaughtExceptionHandler
接口:
public interface UncaughtExceptionHandler { void uncaughtException(Thread t, Throwable e); }
在JDK5.0中,可以为任何一个Thread设置一个UncaughtExceptionHandler,通过调用Thread类的setUncaughtExceptionHandler方法:
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh)
其作用是当某个线程执行某段代码出现未捕获的异常而停止了,可以通过这个UncaughtExceptionHandler对这个异常进行处理,例如记录错误日志。
当然你也可以为所有Thread设置一个全局的默认UncaughtExceptionHandler,通过调用Thread类的静态方法
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)
通常不建议使用,实际开发中,代码都是由多个模块组成的,例如两个模块的开发人员都要设置全局的UncaughtExceptionHandler,那么就有了冲突。最好的办法就是不设置全局的,只对这个模块创建的线程进行单独设置。
需要注意的是:
当线程有未捕获的异常,在回调UncaughtExceptionHandler的uncaughtException方法时,线程已经停止了。这个时候对异常进行处理,其实作用也不大,毕竟线程停止已经没法继续处理任务了。
当然你可以进行补救,例如创建一个新的线程来运行任务。
这里不得不提ThreadGroup与线程池(ThreadPoolExecutor)的区别:
ThreadGroup:管理的是线程,需要手工创建线程添加到ThreadGroup中
线程池:管理的是线程+任务。我们可以指定线程池的核心线程数,最大线程数,以及任务队列等。当某个线程出现未捕获的异常而中断时,线程池也可以自动帮我们来创建新的线程,这是通过线程工厂(ThreadFactory来实现的)。
所以,千万不要把ThreadGroup和线程池放到同一个层面来进行比较,二者的关注点本来就不同。一个关注的是对线程的管理,一个关注的是如何维护指定数量的线程来运行任务。
事实上,因为一个线程池中通常都是运行同一类任务,所以我们可以把一个线程池中创建的所有线程都放到同一个ThreadGroup中。之后在线程池需要停止的时候,可以利用ThreadGroup的一些管理方法,如interrupt,来中断所有的线程。
引入ThreadGroup的另外一个原因:安全。Java支持在运行时对代码运行执行的操作执行一些安全性检查工作,一些涉及到安全性的操作,都有对应的权限(Permission
)来描述。Permission有很多,如下:
其中:
BasicPermission
有一个子类RuntimePermission
,表示运行时的权限检查。完整的权限信息请参考RuntimePermission的javadoc,我们仅仅关注于Thread和ThreadGroup相关的权限。
可以看到,关于Thread和ThreadGroup相关的权限是:modifyThread、stopThread、modifyThreadGroup,关于这三个权限的说明和可能存在的安全问题,参考表格后面的说明。
使用步骤通常如下所示:
1、创建一个安全策略文件
在/test目录下创建一个runtime.policy文件(名字随意,通常以.policy结尾)
grant codeBase "file:/C:/classes/remote/*" { permission java.lang.RuntimePermission "modifyThread,stopThread,modifyThreadGroup"; };
2、启动java程序时指定jvm参数指定使用安全管理器,并指定策略文件的位置
如:
java -Djava.security.manager -Djava.security.policy=/test/runtime.policy 主类
最后提示: Java Security的权限管理只有一些极端特殊的项目才有可能使用到,绝大部分情况下是鸡肋。也建议读者非工作需要的话,没有必要去研究这个,因为研究了也用不上,一些人可能一辈子也用不上…
ThreadGroup两大功能:对线程管理和安全,在实际开发中直接使用的比较少,一般我们都是直接使用线程池。