在閱讀ZooKeeper的源碼時,看到這么一個片段,在單機模式啟動的時候,會調用下面的方法,根據zoo.cfg的配置啟動單機版本的服務器:
public void runFromConfig(ServerConfig config) throws IOException {
//1 創建ZooKeeper服務器
final ZooKeeperServer zkServer = new ZooKeeperServer();
final CountDownLatch shutdownLatch = new CountDownLatch(1);
zkServer.registerServerShutdownHandler(
new ZooKeeperServerShutdownHandler(shutdownLatch));
...
//2 創建ZooKeeper的NIO線程
cnxnFactory = ServerCnxnFactory.createFactory();
cnxnFactory.configure(config.getClientPortAddress(),
config.getMaxClientCnxns());
cnxnFactory.startup(zkServer);
// 3 調用shutdownLatch阻塞,服務器在新線程正常處理請求
shutdownLatch.await();
// 4 如果有錯誤,直接調用shutdown
shutdown();
// 5 執行cnx.join()等待NIO線程關閉
cnxnFactory.join();
if (zkServer.canShutdown()) {
zkServer.shutdown(true);
}
}
...
protected void shutdown() {
if (cnxnFactory != null) {
cnxnFactory.shutdown();
}
}
其中比較有意思的兩個地方:
1 CountDownLatch的使用
開啟NIO新線程接收客戶端的請求,服務端的主線程直接利用countdownlatch掛起。這個CountDownLatch之前有說過,就是個多線程的計數器。
詳細內容參考文章——Java計數器之CountDownLatch、CyclicBarrier、Semaphore
2 join的作用
join的作用就是主線程遇到A.join()之后,就會掛起;登到A結束后,才會繼續執行。可以理解為主線程調用了A的wait方法...
下面有個Join的小例子:
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread A = new Thread(()->{
System.out.println("進入線程A");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A:線程A結束");
});
A.start();
System.out.println("Main:等待線程A結束");
A.join();
System.out.println("Main:線程A已經結束,退出");
}
}
輸出內容為:
Main:等待線程A結束
進入線程A
A:線程A結束
Main:線程A已經結束,退出
可以看到,Main線程在A.join()的地方掛起了。等A結束后,才繼續執行。
ZooKeeper這樣的設計,使服務器在關閉前,確保NIO線程正確關閉,避免NIO資源的未釋放。