目录
第六章、线程管理
6.1 线程组
类似于在计算机中使用文件夹管理文件,也可以使用线程组来管理线程。在线程祖中定义一组相似(相关)的线程,在线程组中也可以定义子线程组。
Thread类有几个构造方法允许在创建线程时指定线程组,如果在创建线程时没有指定线程组则该线程就属于父线程所在的线程组。JVM在创建main线程时会为它指定一个线程组,因此每个java线程都有一个线程组与之关联,可以调用线程的getThreadGroup()方法返回线程组。
线程组开始是处于安全的考虑设计用来区分不同的Applet,然而ThreadGroup并未实现这一目标,在新开发的系统中,已经不常用线程组,现在一般会将一组相关的线程存入一个数组或一个集合中,如果仅仅是用来区分线程时,可以使用线程名来区分,多数情况下,可以忽略线程组。
main线程组的父线程组是system。
线程组也是它自己的父线程组。
6.1.1 创建线程组
类java.lang.ThreadGroup。
构造方法:
①ThreadGroup(String name);
②ThreadGroup(ThreadGroup parent, String name);
public class Test {
public static void main(String[] args) throws InterruptedException {
//返回当前main线程的线程组
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
System.out.println(mainGroup);
//定义线程组,如果不指定所属线程组,则自定归属当前线程所属的线程组中
ThreadGroup group1 = new ThreadGroup("group1");
System.out.println(group1);
//创建一个线程组,同时指定父线程组
ThreadGroup group2 = new ThreadGroup(mainGroup,"group2");
//现在group1与group2都是mainGroup线程组中的子线程组
System.out.println(group1.getParent() == mainGroup);//getParent()方法返回父线程组
System.out.println(group2.getParent() == mainGroup);
//在创建线程时指定所属线程组
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread());
}
};
//在创建线程时,如果没有指定线程组,则默认线程归属到父线程的线程组中(此时的父线程就是main线程)
Thread t1= new Thread(r,"t1");
System.out.println(t1);//Thread[t1,5,main] t1的线程组是main线程组
//指定线程所属的线程组
Thread t2 = new Thread(group1,r,"t2");
System.out.println(t2);//Thread[t2,5,group1]
}
}
6.2.2 线程组的基本操作
int activeCount(); 返回当前线程组及子线程组中活动线程的数量(近似值)。
int activeGroupCount(); 返回当前线程组及子线程组中活动线程组的数量(近似值)。
int enumerate(Thread[] list, boolean recurse); 把当前线程组和子线程组中所有的线程复制到参数数组中。
int enumerate(Thread[] list, boolean recurse); 如果第二个参数设置为false,则只复制当前线程组中所有的线程,不复制子线程组中的线程。
int enumerate(ThreadGroup[] list); 将当前线程组和子线程组中所有的活动线程组复制到参数数组中。
int enumerate(ThreadGroup[] list, boolean recurse); 第二个参数设置为false,则只复制当前线程组的子线程组,不复制子线程组中的线程组。
getMaxPriority(); 返回线程组的最大优先级,默认是10。
getName(); 返回线程组的名称。
getParent(); 返回父线程组。
interrupt(); 中断线程组中所有的线程。可以给该线程组中所有的活动线程添加中断标志。
isDaemon(); 判断当前线程是否为守护线程组。
list(); 将当前线程中的活动线程打印出来。
parebtOf(ThreadGroup g); 判断当前线程组是否为参数线程组的父线程组。
setDaemon(boolean daemon); 将参数设置为true,就可以把当前线程组设置为守护线程组。当守护线程中没有任何活动线程时,守护线程组会自动销毁。注意线程组的守护属性,不影响线程组中线程的守护属性,或者说守护线程组中的线程可以是非守护线程。
6.2 捕获线程的执行异常
在线程的run方法中,如果有受检异常(编译时异常)必须进行捕获处理,如果想要获得run()方法中出现的非受检异常(运行时异常),可以通过UncaughtExceptionHandler接口获得哪个线程出现了运行时异常。在Thread类中有关处理运行异常的方法有:
①getDefaultUncaughtExceptionHandler() 获得全局的(默认的)UncaughtExceptionHandler,
是一个静态方法。
②setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 设置全局
的UncaughtExceptionHandler,是一个静态方法。
②getUncaughtExceptionHandler() 获得当前线程的UncaughtExceptionHandler,是一个实
例方法。
③setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 设置当前线程的
UncaughtExceptionHandler,是一个实例方法。
在线程运行过程中出现异常,JVM(也只有JVM可以调用)会调用Thread类的dispatchUncaughtException(Throwable e)方法,该方法会调用getUncaughtExceptionHandler().uncaughtException(this,e); 如果想要获得线程中出现异常的信息,就需要设置线程的UncaughtExceptionHandle。
//演示设置线程的UncaughtExceptionHandler接口
public class Test {
public static void main(String[] args) throws InterruptedException {
//1.设置线程的全局的回调接口
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
//t参数接收发生异常的线程,e就是该线程中的异常
System.out.println(t.getName() + "线程产生了异常:" + e.getMessage());
}
});
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始运行");
try {
Thread.sleep(2000);//受检异常必须捕获处理
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(12 / 0);//会产生算术异常
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
String txt = null;
System.out.println(txt.length());//会产生空指针异常
}
}).start();
/*
在实际开发中,这种设计异常处理的方式还是比较常用的,尤其是异常执行的方法。
如果线程产生了异常,JVM会调用dispatchUncaughtException()方法,在该方法中调用了getUncaughtExceptionHandler().uncaughtException(this, e);
如果当前线程设置了UncaughtExceptionHandler回调接口就直接调用它自己的uncaughtException方法;
如果没有设置则调用当前线程所在线程组的UncaughtExceptionHandler回调接口的uncaughtException方法;
如果线程组也没有设置该回调接口,则直接把异常的栈信息定向到System.err中。
*/
}
}
6.3 注入Hook钩子线程
现在很多软件包括MySQL、Zookeeper、kafka等都存在Hook线程的校验机制,目的是校验进程是否已启动,防止重复启动程序。
Hook线程也称为钩子线程,当JVM退出的时候会执行Hook线程。经常在程序启动时创建一个