Thread和ThreadPool的Shutdown Hook

本文给出在线程和线程池中使用Shutdown Hook的具体方法。

ShutdownHook应用场景

Java程序经常也会遇到进程挂掉的情况,一些状态没有正确的保存下来,这时候就需要在JVM关掉的时候执行一些清理现场的代码。JAVA中的ShutdownHook提供了比较好的方案。
JDK提供了Java.Runtime.addShutdownHook(Thread hook)方法,可以注册一个JVM关闭的钩子,这个钩子可以在一下几种场景中被调用:

  1. 程序正常退出
  2. 使用System.exit()
  3. 终端使用Ctrl+C触发的中断
  4. 系统关闭
  5. OutOfMemory宕机
  6. 使用Kill pid命令干掉进程(注:在使用kill -9 pid时,是不会被调用的)

线程+ShutdownHook

这是一个最简单的使用ShutdownHook的例子,用于解释ShutdownHook的用法。

public class ThreadHookTest {

    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new ThreadHook());
        try {
            System.out.println("Start sleep");
            Thread.sleep(3000);
        }catch (Exception e){
            System.out.println("Sleep error");
        }
    }

    static class ThreadHook extends Thread {
        @Override
        public void run() {
            System.out.println("Do some clean work and bye");
        }
    }
}

使用方法就是:通过Runtime.getRuntime()获取当前正在运行的Java application的runtime object(这个runtime object的作用就是让正在运行的Java application可以与外部做交互),然后通过addShutdownHook()方法为这个Java application添加一个Hook线程,让程序在退出的时候可以做一些清理工作。
运行代码,不论是程序正常结束,还是在运行过程中在终端人为干预导致意外中断,都会输出:

Start sleep
Do some clean work and bye

线程+ShutdownHook实际应用

上面例子的清理工作是输出字符串“Do some clean work and bye”具体的应用,看不出什么实际用处。下面列举一个实际应用:
从一个路径下读取所有txt文件,逐行打印。如果在这个过程中发生意外(人为干预,强制退出),ProcessorHook 可以打印退出时正在处理的文件及这个文件的状态。

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;

public class FilesProcessor {

    public static String status = "STOPPED";
    public static String fileName = "";

    public static void main(String[] args) {
        String directory = "E:\\Learning\\ThreadPool\\testdir";
        Runtime.getRuntime().addShutdownHook(new ProcessorHook());
        File dir = new File(directory);

        File[] txtFiles = dir.listFiles(new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                if (name.endsWith(".txt"))
                    return true;
                else
                    return false;
            }
        });

        for (File file : txtFiles) {
            System.out.println(file.getName());
            BufferedReader reader = null;
            status = "STARTED";
            fileName = file.getName();
            try {
                FileReader fr = new FileReader(file);
                reader = new BufferedReader(fr);
                String line;

                line = reader.readLine();

                while (line != null) {
                    System.out.println(line);
                    Thread.sleep(1000); // assuming it takes 1 second to process each record
                    // read next line
                    line = reader.readLine();
                }
                status = "PROCESSED";
            } catch (IOException | InterruptedException e) {
                status = "ERROR";
                e.printStackTrace();
            }finally{
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        status="FINISHED";
    }
}
public class ProcessorHook extends Thread {

    @Override
    public void run(){
        System.out.println("Status="+FilesProcessor.status);
        System.out.println("FileName="+FilesProcessor.fileName);
        if(!FilesProcessor.status.equals("FINISHED")){
            System.out.println("Seems some error, sending alert");
        }

    }
}

该例出处:Java Shutdown hook – Runtime.addShutdownHook()

线程池+ShutdownHook

在并发编程中,常常用到线程池,线程池中包括多个线程,给每一个线程都启一个ShutdownHook的做法会造成资源浪费。为了给线程池加一个ShutdownHook,可以将线程池服务ExecutorService 作为一个成员变量,在主类中定义一个继承自Thread类的内部类,让这个内部类完成线程池清理的工作。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * Created by Chang Zhichao on 2018/10/22.
 */
public class ThreadPoolTest {

    private static ExecutorService service = Executors.newCachedThreadPool();

    public static void main(String[] args) {

        Runtime.getRuntime().addShutdownHook(new ClearWork());
        service.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                }catch (Exception e){
                    System.out.println("Sleep error");
                }
            }
        });

        service.shutdown();
        System.out.println(" ===> main Thread execute here ! ");

    }

    static class ClearWork extends Thread {

        @Override
        public void run() {
            System.out.println("启动CleanWork线程");

            try {
                while (!service.awaitTermination(500, TimeUnit.MILLISECONDS)) {//每隔0.5s轮询一次,可以在这里输出线程池在关闭过程中的行为日志
                    System.out.println("正在等待main进程执行结束");
                }
            } catch (Exception e) {
                System.out.println("在等待main进程执行结束的过程中发生异常");
            }
        }
    }
}

主程序设计为sleep(5000),在其运行期间如果强制停止程序,会看到ClearWork线程执行相关操作,在实际应用中可以在该线程中设计相应的保护措施。

你可能感兴趣的:(Thread和ThreadPool的Shutdown Hook)