背景
我們提供的服務有:網(wǎng)站建設、做網(wǎng)站、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認證、雁峰ssl等。為上1000家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務,是有科學管理、有技術的雁峰網(wǎng)站制作公司有朋友反饋zk連接很慢。整理出zk連接的關鍵邏輯如下:
上面的代碼造成第一次調(diào)用ClientZkAgent.getInstance的時候,需耗時10s, 這個時間恰好跟semaphore的超時時間相當. 在此期間,整個世界好像停滯了一樣。
分析
在本地重現(xiàn)后,通過jstack獲得系統(tǒng)停滯期間的線程棧,發(fā)現(xiàn)這個時候zookeeper的EventThread有個比較奇怪的現(xiàn)象:
客戶端實際上很快就連上了zookeeper并返回后生成了SyncConnected事件,而且EventThread已經(jīng)在回調(diào)Watcher.process方法了,但似乎事件線程就一直hold在上面#_1的位置無法往下走, 同時,lambda表達式變成了ClientZkAgent的一個方法了:lambda$connect$0。
了解了一下Java中l(wèi)ambda的實現(xiàn)方式,事情水落石出了。
簡而言之,jvm會把lambda表達式轉(zhuǎn)換成所在類的一個方法lambda${method}${seq}(method為該lambda所在的方法名,例如上面的connect方法),同時通過動態(tài)代理生成一個代理類(該代理類實現(xiàn)了lambda表達式所代表的具體接口),在該代理類中調(diào)用lambda${method}${seq}。
在上面的例子中,生成的代理類大概如下:
再梳理一下:
業(yè)務線程:
1.通過靜態(tài)方法ClientZkAgent.getInstance()獲取實例,第一次訪問的時候會觸發(fā)類ClientZkAgent的裝載。
2.裝載過程中,裝載靜態(tài)成員instance,這時候會嘗試創(chuàng)建一個ClientZkAgent對象。
3.在ClientZkAgent的構(gòu)造函數(shù)中連接zk,并通過CountdownLatch進入阻塞狀態(tài)。注意這時候類裝載還沒完成。
4.CountdownLatch超時后完成對象的初始化以及整個類的加載
zk事件線程:
SyncConnected事件觸發(fā)后,調(diào)用ClientZkAgent.lambda$connect$0(event), 試圖喚醒業(yè)務線程(喚醒邏輯在lambda中)。
然而這時候ClientZkAgent還沒加載完,事件線程只能等待類加載流程的結(jié)束。
業(yè)務線程加載完ClientZkAgent后,事件線程完成事件的處理。
可見,在這個過程中,兩個線程相互等待(類似死鎖但不是死鎖),直至業(yè)務線程超時后才化解這個局面。
歡迎大家關注我的公種浩【程序員追風】,文章都會在里面更新,整理的資料也會放在里面。
解決
修改ClientZkAgent的初始化邏輯如下:
最后
歡迎大家一起交流,喜歡文章記得點個贊,感謝支持!
另外有需要云服務器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務器、裸金屬服務器、高防服務器、香港服務器、美國服務器、虛擬主機、免備案服務器”等云主機租用服務以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應用場景需求。