ShutdownHookとは
JVMが整然とシャットダウンされた場合には、JVMは登録されているシャットダウンフックを実行します。
整然としたシャットダウンとは以下のものを指します。
- デーモンでないスレッドが全て終了した
- System.exitが呼ばれた
- ユーザ割り込みでの停止(Ctrl + C、SIGINT)
実装例
シャットダウンフックはRuntimeのaddShutdownHookメソッドで登録します。
ShutdownTest.java
package demo;
/**
* Shutdownイベントのテストを行うクラスです。
*
*/
public class ShutdownTest {
public static void main(String[] args) {
//シャットダウンフックを登録します。
Runtime.getRuntime().addShutdownHook(new Thread(
() -> System.out.println("shutdown..")
));
System.out.println("processed");
}
}
複数の処理を行うには
シャットダウンフックに登録した処理は並列で処理されます。
ShutdownTest2.java
package demo;
/**
* Shutdownイベントのテストを行うクラスです。
*
*/
public class ShutdownTest2 {
public static void main(String[] args) {
//シャットダウンフックを登録します。
Runtime.getRuntime().addShutdownHook(new Thread(
() -> {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("shutdown A..");
}
));
Runtime.getRuntime().addShutdownHook(new Thread(
() -> System.out.println("shutdown B..")
));
System.out.println("processed");
}
}
実行すると、"shutdown B.."の方が先に出力されます。
以下のようにシャットダウンイベントを管理するクラスを実装することで、処理順を制御することができます。
ShutdownManager.java
package demo;
import java.util.ArrayList;
import java.util.List;
/**
* シャットダウンイベントの管理を行うクラスです。
*
*/
public final class ShutdownManager implements Runnable {
/** ShutdownManagerのSingletonインスタンス */
private static final ShutdownManager instance = new ShutdownManager();
private ShutdownManager() {}
/** シャットダウンイベントのリスナー */
private List<Runnable> listeners = new ArrayList<Runnable>();
static {
Runtime.getRuntime().addShutdownHook(new Thread(instance));
}
/**
* ShutdownManagerのインスタンスを返却します。
*
* @return ShutdownManagerのインスタンス
*/
public static ShutdownManager getInstance() {
return instance;
}
/**
* リスナーを追加します。
*
* @param listener リスナー
*/
public void addListeners(Runnable listener) {
listeners.add(listener);
}
/**
* シャットダウン処理を行います。
*/
public void run() {
System.out.println("shutdown start");
listeners.forEach(Runnable::run);
System.out.println("shutdown end");
}
}
ShutdownTest3.java
package demo;
/**
* Shutdownイベントのテストを行うクラスです。
*
*/
public class ShutdownTest3 {
public static void main(String[] args) {
//シャットダウンフックを登録します。
ShutdownManager.getInstance().addListeners(
() -> {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("shutdown A..");
}
);
ShutdownManager.getInstance().addListeners(
() -> System.out.println("shutdown B..")
);
System.out.println("processed");
}
}
注意
シャットダウンフックは開始されると他のスレッドと並行して動きます。
他のスレッドの終了を待ちません。
ShutdownTest4.java
package demo;
/**
* Shutdownイベントのテストを行うクラスです。
*
*/
public class ShutdownTest4 {
public static void main(String[] args) {
new Thread(
() -> {
try {
System.out.println("start");
Thread.sleep(1000);
System.out.println("end");
} catch (Exception e) {
e.printStackTrace();
}
}
).start();
Runtime.getRuntime().addShutdownHook(new Thread(
() -> {
try {
System.out.println("shutdown start");
Thread.sleep(1500);
System.out.println("shutdown end");
} catch (Exception e) {
e.printStackTrace();
}
}
));
System.exit(0);
}
}
上の例ではスレッド動作中にSystem.exitしていますが、実行結果は次のようになります。
start
shutdown start
end
shutdown end
リソースの扱いなど、他のスレッドの状況に依存しないようにする必要があります。
ちなみに、System.exit(0)を削除すると以下のような結果になります。
start
end
shutdown start
shutdown end