您好,登錄后才能下訂單哦!
背景
有朋友反饋zk連接很慢。整理出zk連接的關鍵邏輯如下:
上面的代碼造成第一次調用ClientZkAgent.getInstance的時候,需耗時10s, 這個時間恰好跟semaphore的超時時間相當. 在此期間,整個世界好像停滯了一樣。
分析
在本地重現后,通過jstack獲得系統停滯期間的線程棧,發現這個時候zookeeper的EventThread有個比較奇怪的現象:
客戶端實際上很快就連上了zookeeper并返回后生成了SyncConnected事件,而且EventThread已經在回調Watcher.process方法了,但似乎事件線程就一直hold在上面#_1的位置無法往下走, 同時,lambda表達式變成了ClientZkAgent的一個方法了:lambda$connect$0。
了解了一下Java中lambda的實現方式,事情水落石出了。
簡而言之,jvm會把lambda表達式轉換成所在類的一個方法lambda${method}${seq}(method為該lambda所在的方法名,例如上面的connect方法),同時通過動態代理生成一個代理類(該代理類實現了lambda表達式所代表的具體接口),在該代理類中調用lambda${method}${seq}。
在上面的例子中,生成的代理類大概如下:
再梳理一下:
業務線程:
1.通過靜態方法ClientZkAgent.getInstance()獲取實例,第一次訪問的時候會觸發類ClientZkAgent的裝載。
2.裝載過程中,裝載靜態成員instance,這時候會嘗試創建一個ClientZkAgent對象。
3.在ClientZkAgent的構造函數中連接zk,并通過CountdownLatch進入阻塞狀態。注意這時候類裝載還沒完成。
4.CountdownLatch超時后完成對象的初始化以及整個類的加載
zk事件線程:
SyncConnected事件觸發后,調用ClientZkAgent.lambda$connect$0(event), 試圖喚醒業務線程(喚醒邏輯在lambda中)。
然而這時候ClientZkAgent還沒加載完,事件線程只能等待類加載流程的結束。
業務線程加載完ClientZkAgent后,事件線程完成事件的處理。
可見,在這個過程中,兩個線程相互等待(類似死鎖但不是死鎖),直至業務線程超時后才化解這個局面。
歡迎大家關注我的公種浩【程序員追風】,文章都會在里面更新,整理的資料也會放在里面。
解決
修改ClientZkAgent的初始化邏輯如下:
最后
歡迎大家一起交流,喜歡文章記得點個贊,感謝支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。