一個Lambda引發(fā)的坑

1. 背景

上周有小伙伴反饋zk連接很慢躯概。
整理出zk連接的關(guān)鍵邏輯如下:

public class ClientZkAgent {
  //單例模式
  private static final ClientZkAgent instance = new ClientZkAgent();
  private ZooKeeper zk; //zk客戶端
  private ClientZkAgent() {
    connect(); //初始化并連接zk
  }
 
  public static ClientZkAgent getInstance() {
    return instance;
  }

 /**
  * zk常用模式: 由于zookeeper的連接是異步的允睹,為防止zk對象在建立有效連接之前就返回行您,
  * 我們阻塞主線程卸察,并通過zookeeper的EventThread在連接事件中喚醒主線程
  */
 private void connect() {
    CountDownLatch semaphore = new CountDownLatch(1);
    zk = new ZooKeeper(zkHost, timeout, watchEvent -> { // #_1
        switch (e.getState()) {
           case SyncConnected:
                semaphore.countDown();
                break;
                // 其它邏輯 ....
        }
     });
    
    semaphore.await(10000, TimeUnit.MILLISECONDS);
 }
}

上面的代碼造成第一次調(diào)用ClientZkAgent.getInstance的時候,需耗時10s璧微, 這個時間恰好跟semaphore的超時時間相當. 在此期間,整個世界好像停滯了一樣硬梁。

2. 分析

在本地重現(xiàn)后前硫,通過jstack獲得系統(tǒng)停滯期間的線程棧,發(fā)現(xiàn)這個時候zookeeperEventThread有個比較奇怪的現(xiàn)象:

"main-EventThread" #13 daemon prio=5 os_prio=0 tid=0x000000001fe36800 nid=0xf0c in Object.wait() [0x000000002032f000]
   java.lang.Thread.State: RUNNABLE
    at com.github.dapeng.registry.zookeeper.ClientZkAgent.lambda$connect$0(ClientZkAgent.java:154)
    at com.github.dapeng.registry.zookeeper.ClientZkAgent$$Lambda$1/116211441.process(Unknown Source)
    at org.apache.zookeeper.ClientCnxn$EventThread.processEvent(ClientCnxn.java:533)
    at org.apache.zookeeper.ClientCnxn$EventThread.run(ClientCnxn.java:508)

   Locked ownable synchronizers:
    - None

客戶端實際上很快就連上了zookeeper并返回后生成了SyncConnected事件荧止,而且EventThread已經(jīng)在回調(diào)Watcher.process方法了屹电,但似乎事件線程就一直hold在上面#_1的位置無法往下走, 同時跃巡,lambda表達式變成了ClientZkAgent的一個方法了:lambda$connect$0危号。

了解了一下Java中lambda的實現(xiàn)方式,事情水落石出了素邪。

簡而言之外莲,jvm會把lambda表達式轉(zhuǎn)換成所在類的一個方法lambda${method}${seq}(method為該lambda所在的方法名,例如上面的connect方法)兔朦,同時通過動態(tài)代理生成一個代理類(該代理類實現(xiàn)了lambda表達式所代表的具體接口)偷线,在該代理類中調(diào)用lambda${method}${seq}
在上面的例子中沽甥,生成的代理類大概如下:

final class ClientZkAgent$$Lambda$1 implements Watcher {
     final ClientZkAgent clientZkAgent;
    
     public void process(WatchedEvent event) {
        clientZkAgent.lambda$connect$0(event);
    }
}

再梳理一下:
業(yè)務(wù)線程:

  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事件線程:

  1. SyncConnected事件觸發(fā)后,調(diào)用ClientZkAgent.lambda$connect$0(event), 試圖喚醒業(yè)務(wù)線程(喚醒邏輯在lambda中)材失。
  2. 然而這時候ClientZkAgent還沒加載完痕鳍,事件線程只能等待類加載流程的結(jié)束。
  3. 業(yè)務(wù)線程加載完ClientZkAgent后龙巨,事件線程完成事件的處理笼呆。

可見,在這個過程中旨别,兩個線程相互等待(類似死鎖但不是死鎖)诗赌,直至業(yè)務(wù)線程超時后才化解這個局面。

3. 改進

修改ClientZkAgent的初始化邏輯如下:

public class ClientZkAgent {
  //單例模式
  private static final ClientZkAgent instance = new ClientZkAgent();
  private ZooKeeper zk; //zk客戶端
  private ClientZkAgent() {
  }
 
  public static ClientZkAgent getInstance() {
     if (instance.zk == null) {
            synchronized(ClientZkAgent.class) {
                if (instance.zk == null) {
                    instance.connect();
                }
            }
        }
        return instance;
  }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秸弛,一起剝皮案震驚了整個濱河市铭若,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌递览,老刑警劉巖叼屠,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異绞铃,居然都是意外死亡镜雨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進店門儿捧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荚坞,“玉大人,你說我怎么就攤上這事菲盾⊥怯埃” “怎么了?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵懒鉴,是天一觀的道長诡挂。 經(jīng)常有香客問我,道長临谱,這世上最難降的妖魔是什么咆畏? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮吴裤,結(jié)果婚禮上旧找,老公的妹妹穿的比我還像新娘。我一直安慰自己麦牺,他們只是感情好钮蛛,可當我...
    茶點故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布鞭缭。 她就那樣靜靜地躺著,像睡著了一般魏颓。 火紅的嫁衣襯著肌膚如雪岭辣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天甸饱,我揣著相機與錄音沦童,去河邊找鬼。 笑死叹话,一個胖子當著我的面吹牛偷遗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播驼壶,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼氏豌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了热凹?” 一聲冷哼從身側(cè)響起泵喘,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎般妙,沒想到半個月后纪铺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡碟渺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年鲜锚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片止状。...
    茶點故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡烹棉,死狀恐怖攒霹,靈堂內(nèi)的尸體忽然破棺而出怯疤,到底是詐尸還是另有隱情,我是刑警寧澤催束,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布集峦,位于F島的核電站,受9級特大地震影響抠刺,放射性物質(zhì)發(fā)生泄漏塔淤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一速妖、第九天 我趴在偏房一處隱蔽的房頂上張望高蜂。 院中可真熱鬧,春花似錦罕容、人聲如沸备恤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽露泊。三九已至喉镰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惭笑,已是汗流浹背侣姆。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留沉噩,地道東北人捺宗。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像屁擅,于是被迫代替她去往敵國和親偿凭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容