在Thread类中,关于处理运行时运行异常的API总共有四个, 如下所示:
- public void setUncaughtExcetionHandler(UncaughtExceptionHandler eh) 为某个特定线程指定UnCaughtExceptionHandler
- public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) 设置全局的UnCaughtExceptionHandler
- public UncaughtExceptionHandler getUncaughtExceptionHandler() 获取特定线程的UncaughtExceptionHandler
- public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() 获取全局的UncaughtExceptionHandler
UncaughtExceptionHandler介绍
线程在执行单元中是不允许抛出checked异常的, 而且线程运行在自已的上下文中, 派生它的线程将无法直接获得运行中出现的异常信息。对此Java为我们提供了一个UncaughtExceptionHandler接口, 从而得知哪个线程在运行时出错,以及出什么样的错误。
public class CaptureThreadException {
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler((thread, e) -> {
System.out.println(thread.getName() + " occur exception");
e.printStackTrace();
});
final Thread thread = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
// 抛出异常
System.out.println(1/0);
}, "Test-Thread");
thread.start();
}
}
打印
Test-Thread occur exception
java.lang.ArithmeticException: / by zero
at com.shinley.concurrent.chapter7.CaptureThreadException.lambda$main$1(CaptureThreadException.java:20)
at java.lang.Thread.run(Thread.java:748)
注入钩子线程
Jvm进程的退出是由于JVM进程中没有活跃的非守护线程,或者收到了系统中断信号, 向JVM程序注入一个Hook线程, JVM进程退出的时候, Hook线程会启动执行。
通过Runtime可以为JVM注入多个Hook线程。
public class ThreadHook {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.out.println("the hook thread 1 is running");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 可以多次注入
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 application will be stopped");
}
}
打印
The application will be stopped
the hook thread 2 is running
the hook thread 1 is running
Hook线程实战
我们在开发中经常遇到Hook线程, 比如为了防止某个程序被重复启动, 在进程启动时会创建一个lock文件, 进程收到中断信号的时候会删除这个lock 文件, 我们在mysql服务吕, zookeeper, kafka等系统中都能看到lock文件的存在。 以下为利用hook线程的特点, 模拟一个防止重复启动的程序。
public class PreventDuplicated {
private final static String LOCK_PATH = System.getProperty("user.home");
private final static String LOCK_FILE = ".lick";
private final static String PERMISSIONS = "rw------";
public static void main(String[] args) throws IOException {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("the application received kill SIGNAL");
getLockFile().toFile().delete();
}));
checkRunning();
for (;;) {
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static void checkRunning() throws IOException {
Path path = getLockFile();
if (path.toFile().exists()) {
throw new RuntimeException("The application already running");
}
Set<PosixFilePermission> perms = PosixFilePermissions.fromString(PERMISSIONS);
Files.createFile(path, PosixFilePermissions.asFileAttribute(perms));
}
private static Path getLockFile() {
return Paths.get(LOCK_PATH, LOCK_FILE);
}
}
- Hook线程 只有在收到退出信号的时候会被执行, 如果kill 的时候使用了-9参数, 那么Hook线程不会得到执行, 进程 将会立即退出, 因些.lock文件得不到清理
- Hook线程中也可以执行一些资源释放的工作, 比如关闭文件句柄, socket链接, 数据库connection等
- 尽量不要在Hook线程中执行一些耗时百较长的操作, 因为其会导致程序迟迟不能退出。