ThreadGroup介绍
ThreadGroup并不能提供对线程的管理,ThreadGroup的主要功能是对线程进行组织,下面将详细介绍ThreadGroup的主要方法。
ThreadGroup的基本操作
activeCount()用于获取group中活跃的线程,这只是个估计值,并不能百分之百地保证数字一定正确,原因前面已经分析过,该方法会递归获取其他子group中的活跃线程。
activeGroupCount()用于获取group中活跃的子group,这也是一个近似估值,该方法也会递归获取所有的子group。
getMaxPriority()用于获取group的优先级,默认情况下,Group的优先级为10,在该group中,所有线程的优先级都不能大于group的优先级。
getName()用于获取group的名字。
getParent()用于获取group的父group,如果父group不存在,则会返回null,比如system group的父group就为null。 list()该方法没有返回值,执行该方法会将group中所有的活跃线程信息全部输出到控制台,也就是System.out。
parentOf(ThreadGroup g)会判断当前group是不是给定group的父group,另外如果给定的group就是自己本身,那么该方法也会返回true。
setMaxPriority(int pri)会指定group的最大优先级,最大优先级不能超过父group的最大优先级,执行该方法不仅会改变当前group的最大优先级,还会改变所有子group的最大优先级。 下面我们给出一个简单的例子来测试一下上面的几个方法。
package com.wangwenjun.concurrent.chapter06;
import java.util.concurrent.TimeUnit;
public class ThreadGroupBasic
{
public static void main(String[] args) throws InterruptedException
{
/*
* Create a thread group and thread.
*/
ThreadGroup group = new ThreadGroup("group1");
Thread thread = new Thread(group, () ->
{
while (true)
{
try
{
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}, "thread");
thread.setDaemon(true);
thread.start();
//make sure the thread is started
TimeUnit.MILLISECONDS.sleep(1);
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
System.out.println("activeCount=" + mainGroup.activeCount());
System.out.println("activeGroupCount=" + mainGroup.activeGroupCount());
System.out.println("getMaxPriority=" + mainGroup.getMaxPriority());
System.out.println("getName=" + mainGroup.getName());
System.out.println("getParent=" + mainGroup.getParent());
mainGroup.list();
System.out.println("--------------------------");
System.out.println("parentOf="+mainGroup.parentOf(group));
System.out.println("parentOf="+mainGroup.parentOf(mainGroup));
}
}
这里需要特别说明的是setMaxPriority,我们通过分析源码得出结论,线程的最大优先级,不能高于所在线程组的最大优先级,但是如果我们把代码写成下面这样会怎么样呢?
ThreadGroup的其他方法
ThreadGroup的interrupt
interrupt一个thread group会导致该group中所有的active线程都被interrupt,也就是说该group中每一个线程的interrupt标识都被设置了
ThreadGroup的destroy
destroy用于销毁ThreadGroup,该方法只是针对一个没有任何active线程的group进行一次destroy标记,调用该方法的直接结果是在父group中将自己移除:
钩子线程
JVM进程的退出是由于JVM进程中没有活跃的非守护线程,或者收到了系统中断信号,向JVM程序注入一个Hook线程,在JVM进程退出的时候,Hook线程会启动执行,通过Runtime可以为JVM注入多个Hook线程,下面就通过一个简单的例子来看一下如何向Java程序注入Hook线程。
ThreadHook.java package com.wangwenjun.concurrent.chapter07;
import java.util.concurrent.TimeUnit;
public class ThreadHook
{
public static void main(String[] args)
{
//为应用程序注入钩子线程
Runtime.getRuntime().addShutdownHook(new Thread()
{
@Override
public void run()
{
try
{
System.out.println("The hook thread 1 is running.");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("The hook thread 1 will exit.");
}
});
//钩子线程可注册多个
Runtime.getRuntime().addShutdownHook(new Thread()
{
@Override
public void run()
{
try
{
System.out.println("The hook thread 2 is running.");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("The hook thread 2 will exit.");
}
});
System.out.println("The program will is stopping.");
}
}
在上面代码中,给Java程序注入了两个Hook线程,在main线程中结束,也就是JVM中没有了活动的非守护线程,JVM进程即将退出时,两个Hook线程会被启动并且运行,输出结果如下:
在我们的开发中经常会遇到Hook线程,比如为了防止某个程序被重复启动,在进程启动时会创建一个lock文件,进程收到中断信号的时候会删除这个lock文件,我们在mysql服务器、zookeeper、kafka等系统中都能看到lock文件的存在,本节中,将利用hook线程的特点,模拟一个防止重复启动的程序,
用hook线程模拟防重复启动程序
package com.wangwenjun.concurrent.chapter07;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class PreventDuplicated
{
private final static String LOCK_PATH = "/home/wangwenjun/locks/";
private final static String LOCK_FILE = ".lock";
private final static String PERMISSIONS = "rw-------";
public static void main(String[] args) throws IOException
{
//① 注入Hook线程,在程序退出时删除lock文件
Runtime.getRuntime().addShutdownHook(new Thread(() ->
{
System.out.println("The program received kill SIGNAL.");
getLockFile().toFile().delete();
}));
//② 检查是否存在.lock文件
checkRunning();
//③ 简单模拟当前程序正在运行
for (; ; )
{
try
{
TimeUnit.MILLISECONDS.sleep(1);
System.out.println("program is running.");
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
private static void checkRunning() throws IOException
{
Path path = getLockFile();
if (path.toFile().exists())
throw new RuntimeException("The program already running.");
Set<PosixFilePermission> perms = PosixFilePermissions.fromString(PERMISS-IONS);
Files.createFile(path, PosixFilePermissions.asFileAttribute(perms));
}
private static Path getLockFile()
{
return Paths.get(LOCK_PATH, LOCK_FILE);
}
}
运行上面的程序,会发现在/home/wangwenjun/locks目录下多了一个.lock文件,程序运行则会创建.lock文件以防止重复启动
执行kill pid或者kill-1 pid命令之后,JVM进程会收到中断信号,并且启动hook线程删除.lock文件。
Hook线程是一个非常好的机制,可以帮助程序获得进程中断信号,有机会在进程退出之前做一些资源释放的动作,或者做一些告警通知。切记如果强制杀死进程,那么进程将不会收到任何中断信号。
注:笔记来自书籍《java高并发编程详解》