0
点赞
收藏
分享

微信扫一扫

【java读书笔记】ThreadGroup和钩子线程的使用


文章目录

  • ​​ThreadGroup介绍​​
  • ​​ThreadGroup的基本操作​​
  • ​​ThreadGroup的其他方法​​
  • ​​钩子线程​​
  • ​​用hook线程模拟防重复启动程序​​

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高并发编程详解》


举报

相关推荐

0 条评论