java 钩子线程_java-钩子线程
在線上Java程序中經常遇到進程程掛掉,一些狀態沒有正確的保存下來,這時候就需要在JVM關掉的時候執行一些清理現場的代碼。Java中得ShutdownHook提供了比較好的方案。
JDK在1.3之后提供了Java Runtime.addShutdownHook(Thread hook)方法,可以注冊一個JVM關閉的鉤子,這個鉤子可以在以下幾種場景被調用:
1)程序正常退出
2)使用System.exit()
3)終端使用Ctrl+C觸發的中斷
4)系統關閉
5)使用Kill pid命令干掉進程
注:在使用kill -9 pid是不會JVM注冊的鉤子不會被調用。
在JDK中方法的聲明:
public void addShutdownHook(Thread hook)
參數
hook – 一個初始化但尚未啟動的線程對象,注冊到JVM鉤子的運行代碼。
異常
IllegalArgumentException – 如果指定的鉤已被注冊,或如果它可以判定鉤已經運行或已被運行
IllegalStateException – 如果虛擬機已經是在關閉的過程中
SecurityException – 如果存在安全管理器并且它拒絕的RuntimePermission(“shutdownHooks”)
代碼示例:
使用Timer模擬一個工作線程,該線程重復工作十次,使用System.exit()退出,在清理現場代碼CleanWorkThread 中,取消timer運行,并輸出必要的日志信息。
package com.netease.test.java.lang;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
/** * Date: 14-6-18 * Time: 11:01 * 測試ShutdownHook */
public class TestShutdownHook {
//簡單模擬干活的
static Timer timer = new Timer("job-timer");
//計數干活次數
static AtomicInteger count = new AtomicInteger(0);
/** * hook線程 */
static class CleanWorkThread extends Thread{
@Override
public void run() {
System.out.println("clean some work.");
timer.cancel();
try {
Thread.sleep(2 * 1000);//sleep 2s
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
//將hook線程添加到運行時環境中去
Runtime.getRuntime().addShutdownHook(new CleanWorkThread());
System.out.println("main class start ..... ");
//簡單模擬
timer.schedule(new TimerTask() {
@Override
public void run() {
count.getAndIncrement();
System.out.println("doing job " + count);
if (count.get() == 10) { //干了10次退出
System.exit(0);
}
}
}, 0, 2 * 1000);
}
}
當發生了System.exit(int)時,希望在系統退出前,執行一點任務來做一些資源方面的回收操作,ShutdownHook可以達到這個目的,它利用hook思路來實現,有些時候也把它叫作“鉤子“。
假如在系統中通過Runtime.getRuntime().exec()或new ProcessBuilde()啟動了子進程(Process),這個子進程一直在運行中,在當前進程退出時,子進程未必會退出,但此時業務上希望將它退出,就可以利用這一點。例如可以這樣寫:
Runtime.getRuntime().addShutdownHook()
{
new Thread()
{
public void run()
{
processer.destroy();
}
}
}
這里需要注意的是,傳入參數是通過new Thread()創建的線程對象,在Java進程調用exit()時,會調用該線程對象的start()方法將其運行起來,所以不要手工先啟動了。另外,這種回調線程就不要設定為死循環程序,否則就無法退出了。
再簡單闡述下原理,大家也可以自行分析源碼得到答案。
java.lang包下有一個Shutdown類,提供了一個Runnable []hooks數組,數組的長度為10,也就是最多定義10個hook(這與程序寫入多少個線程回調沒有關系,下面會介紹,提供add()方法來注冊新的hook對象)
在java.lang.ApplicationShutdownHooks的static匿名塊中,通過add()方法注冊了一個hook,在run()方法內部的靜態方法runHooks(),它內部用一個IdentityHashMap來存放hook信息,可以通過add()方法來添加,而程序中定義的回調線程都放在了這里,它自身用hook的形式存放在于hooks列表中,如下所示:
class ApplicationShutdownHooks
{
static
{
Shutdown.add(1 /* shutdown hook invocation order*/,
new Runnable()
{
public void run()
{
runHooks();
}
}
);
}
}
當調用Runnable.getRunnable().addShutdownHook(Thread)方法時,會間接調用ApplicationShutdownHooks.add(Thread)將線程放到IdentityHashMap中。
當調用System.exit(int)方法時,會間接調用Shutdown.exit(int)方法,再調用sequence()->runHooks()方法,調用如下所示:
/* Run all registered shutdown hooks */
private static void runHooks() {
for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
try {
Runnable hook;
synchronized (lock) {
// acquire the lock to make sure the hook registered during
// shutdown is visible here.
currentRunningHook = i;
hook = hooks[i];
}
if (hook != null) hook.run();
} catch(Throwable t) {
if (t instanceof ThreadDeath) {
ThreadDeath td = (ThreadDeath)t;
throw td;
}
}
}
}
循環中的一個hook對象就是由ApplicationShutdownHooks的匿名塊定義的,因此會調用ApplicationShutdownHooks類的run()方法,再調用它的runHooks()方法,這個runHooks()方法如下所示:
/* Iterates over all application hooks creating a new thread for each
* to run in. Hooks are run concurrently and this method waits for
* them to finish.
*/
static void runHooks() {
Collection threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
}
for (Thread hook : threads) {
hook.start();
}
for (Thread hook : threads) {
try {
hook.join();
} catch (InterruptedException x) { }
}
}
從這個方法中取出所有的Thread,然后將線程啟動起來,最后通過join()方法等待各個線程結束動作。換句話說,在線程關閉前,對多個回調任務的處理方式是每個任務單獨有一個線程處理,而不是所有的任務在一個線程中串行處理。
Runtime.getRuntime().removeShutdownHook用于刪除一個鉤子線程。
總結
以上是生活随笔為你收集整理的java 钩子线程_java-钩子线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: getset原子性 redis_一文看透
- 下一篇: Python基础(二)--数据类型,运算