Java程式經常會遇到程序掛掉的情況,一些狀態沒有正確的儲存下來,這時候就需要在JVM關掉的時候執行一些清理現場的程式碼。JAVA中的ShutdownHook提供了比較好的方案。而在SOFAJRaft-example模組的CounterServer-main方法中就使用了shutdownHook實現優雅停機。
@Author:Akai-yuan
@更新時間:2023/1/25
JDK提供了Java.Runtime.addShutdownHook(Thread hook)方法,可以註冊一個JVM關閉的勾點這個勾點可以在以下幾種場景中被呼叫:
以下幾種情況中是無法被呼叫的:
Runtime.getRuntime().addShutdownHook(shutdownHook);
該方法指,在JVM中增加一個關閉的勾點,當JVM關閉的時候,會執行系統中已經設定的所有通過方法addShutdownHook新增的勾點,當系統執行完這些勾點後,JVM才會關閉。所以這些勾點可以在JVM關閉的時候進行記憶體清理、物件銷燬、關閉連線等操作。
通過反射獲取到grpcServer範例的shutdown方法和awaitTerminationLimit方法,並新增到勾點函數當中
public static void blockUntilShutdown() {
if (rpcServer == null) {
return;
}
//當RpcFactoryHelper中維護的工廠型別是GrpcRaftRpcFactory時進入if條件內部
if ("com.alipay.sofa.jraft.rpc.impl.GrpcRaftRpcFactory".equals(RpcFactoryHelper.rpcFactory().getClass()
.getName())) {
try {
//反射獲取grpcServer中維護的(io.grpc包下的)server範例
Method getServer = rpcServer.getClass().getMethod("getServer");
Object grpcServer = getServer.invoke(rpcServer);
//反射獲取server範例的shutdown方法和awaitTerminationLimit方法
Method shutdown = grpcServer.getClass().getMethod("shutdown");
Method awaitTerminationLimit = grpcServer.getClass().getMethod("awaitTermination", long.class,
TimeUnit.class);
//新增一個shutdownHook執行緒執行方法
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
shutdown.invoke(grpcServer);
awaitTerminationLimit.invoke(grpcServer, 30, TimeUnit.SECONDS);
} catch (Exception e) {
// Use stderr here since the logger may have been reset by its JVM shutdown hook.
e.printStackTrace(System.err);
}
}
});
//執行awaitTermination方法
Method awaitTermination = grpcServer.getClass().getMethod("awaitTermination");
awaitTermination.invoke(grpcServer);
} catch (Exception e) {
LOG.error("Failed to block grpc server", e);
}
}
}
GrpcServer下的shutdown方法與本文的勾點函數無關,此處再對比分析一下GrpcServer的shutdown方法。
public void shutdown() {
//CAS
//當且僅當期待值為true時(與當前AtomicBoolean型別的started一致),設定為false關閉
if (!this.started.compareAndSet(true, false)) {
return;
}
ExecutorServiceHelper.shutdownAndAwaitTermination(this.defaultExecutor);
GrpcServerHelper.shutdownAndAwaitTermination(this.server);
}
ExecutorServiceHelper#shutdownAndAwaitTermination:
我們可以發現實際上就是在執行ExecutorService 中 的shutdown()、shutdownNow()、awaitTermination() 方法,那麼我們來區別以下這幾個方法
public static boolean shutdownAndAwaitTermination(final ExecutorService pool, final long timeoutMillis) {
if (pool == null) {
return true;
}
// 禁止提交新任務
pool.shutdown();
final TimeUnit unit = TimeUnit.MILLISECONDS;
final long phaseOne = timeoutMillis / 5;
try {
// 等待一段時間以終止現有任務
if (pool.awaitTermination(phaseOne, unit)) {
return true;
}
pool.shutdownNow();
// 等待一段時間,等待任務響應被取消
if (pool.awaitTermination(timeoutMillis - phaseOne, unit)) {
return true;
}
LOG.warn("Fail to shutdown pool: {}.", pool);
} catch (final InterruptedException e) {
// (Re-)cancel if current thread also interrupted
pool.shutdownNow();
// preserve interrupt status
Thread.currentThread().interrupt();
}
return false;
}
1、停止接收新的submit的任務;
2、已經提交的任務(包括正在跑的和佇列中等待的),會繼續執行完成;
3、等到第2步完成後,才真正停止;
1、跟 shutdown() 一樣,先停止接收新submit的任務;
2、忽略佇列裡等待的任務;
3、嘗試將正在執行的任務interrupt中斷;
4、返回未執行的任務列表;
說明:
它試圖終止執行緒的方法是通過呼叫 Thread.interrupt() 方法來實現的,這種方法的作用有限,如果執行緒中沒有sleep 、wait、Condition、定時鎖等應用, interrupt() 方法是無法中斷當前的執行緒的。
所以,shutdownNow() 並不代表執行緒池就一定立即就能退出,它也可能必須要等待所有正在執行的任務都執行完成了才能退出。但是大多數時候是能立即退出的。
當前執行緒阻塞,直到:
然後會監測 ExecutorService 是否已經關閉,返回true(shutdown請求後所有任務執行完畢)或false(已超時)
GrpcServerHelper#shutdownAndAwaitTermination
與ExecutorServiceHelper類中的shutdownAndAwaitTermination方法類似的,該方法將優雅的關閉grpcServer.
public static boolean shutdownAndAwaitTermination(final Server server, final long timeoutMillis) {
if (server == null) {
return true;
}
// disable new tasks from being submitted
server.shutdown();
final TimeUnit unit = TimeUnit.MILLISECONDS;
final long phaseOne = timeoutMillis / 5;
try {
// wait a while for existing tasks to terminate
if (server.awaitTermination(phaseOne, unit)) {
return true;
}
server.shutdownNow();
// wait a while for tasks to respond to being cancelled
if (server.awaitTermination(timeoutMillis - phaseOne, unit)) {
return true;
}
LOG.warn("Fail to shutdown grpc server: {}.", server);
} catch (final InterruptedException e) {
// (Re-)cancel if current thread also interrupted
server.shutdownNow();
// 保持中斷狀態
Thread.currentThread().interrupt();
}
return false;
}