本文给出在线程和线程池中使用Shutdown Hook的具体方法。
ShutdownHook应用场景
Java程序经常也会遇到进程挂掉的情况,一些状态没有正确的保存下来,这时候就需要在JVM关掉的时候执行一些清理现场的代码。JAVA中的ShutdownHook提供了比较好的方案。
JDK提供了Java.Runtime.addShutdownHook(Thread hook)方法,可以注册一个JVM关闭的钩子,这个钩子可以在一下几种场景中被调用:
- 程序正常退出
- 使用System.exit()
- 终端使用Ctrl+C触发的中断
- 系统关闭
- OutOfMemory宕机
- 使用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线程执行相关操作,在实际应用中可以在该线程中设计相应的保护措施。
网友评论