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

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

おすすめ

転載: blog.csdn.net/qq_41358574/article/details/121656702