上周有小伙伴反馈zk连接很慢。整理出zk连接的关键逻辑如下:

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);
 }
}

上面的代码造成第一次调用ClientZkAgent.getInstance的时候,需耗时10s, 这个时间恰好跟semaphore的超时时间相当. 在此期间,整个世界好像停滞了一样。

分析

在本地重现后,通过jstack获得系统停滞期间的线程栈,发现这个时候zookeeper的EventThread有个比较奇怪的现象:

"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已经在回调atcher.process方法了,但似乎事件线程就一直hold在上面#_1的位置无法往下走, 同时,lambda表达式变成了ClientZkAgent的一个方法了:lambda$connect$0。

了解了一下Java中lambda的实现方式,事情水落石出了。

简而言之,jvm会把lambda表达式转换成所在类的一个方法lambda${method}${seq}(method为该 所在的方法名,例如上面的connect方法),同时通过动态代理生成一个代理类(该代理类实现了lambda表达式所代表的具体接口),在该代理类中调用lambda${method}${seq}。

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

再梳理一下:

业务线程:

1.通过静态方法ClientZkAgent.getInstance()获取实例,第一次访问的时候会触发类ClientZkAgent的装载。

2.装载过程中,装载静态成员instance,这时候会尝试创建一个ClientZkAgent对象。

3.在ClientZkAgent的构造函数中连接zk,并通过CountdownLatch进入阻塞状态。注意这时候类装载还没完成。

4.CountdownLatch超时后完成对象的初始化以及整个类的加载

zk事件线程:

SyncConnected事件触发后,调用ClientZkAgent.lambda$connect$0(event), 试图唤醒业务线程(唤醒逻辑在lambda中)。

可见,在这个过程中,两个线程相互等待(类似死锁但不是死锁),直至业务线程超时后才化解这个局面。

解决

修改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;
  }

作者:Ever_00

来源:https://www.jianshu.com/p/b302123532b4

- END -
推荐阅读:
1、

2、

3、

4、

5、

关注Java技术栈公众号在后台回复:Java,可获取一份栈长整理的最新Java 技术干货。

点击「阅读原文」和栈长学更多~

不是我吹,Lambda这个坑你肯定不知道!的更多相关文章

  1. Maven--(一个坑)在settings.xml文件中添加mirrors导致无法新建Maven项目

    这是用新电脑第一次创建Maven项目--当然是一个测试项目.已经差不多忘了该怎样做,所以参考我的博客:http://www.cnblogs.com/wql025/p/4996486.html,这应该是 ...

  2. 关于Lambda表达式访问外部变量

    在<C#高级编程>一书中提到通过Lambda表达式可以访问Lambda表达式块外部的变量 ,这是一个很好的功能(类似Js中的 闭包).但是如果没有正确的使用,会非常危险. 比如下面的事例中 ...

  3. 学习Spark——那些让你精疲力尽的坑

    这一个月我都干了些什么-- 工作上,还是一如既往的写bug并不亦乐乎的修bug.学习上,最近看了一些非专业书籍,时常在公众号(JackieZheng)上写点小感悟,我刚稍稍瞄了下,最近五篇居然都跟技术 ...

  4. C# 字符串按 ASCII码 排序,注意其中的小坑

    https://www.cnblogs.com/similar/p/6739293.html 在和银行做数据对接时,涉及到数据传输时的验签及加密.其中数据签名方案中就要求数据项根据属性名按 ASCII ...

  5. C# 参数签名字符串按 ASCII码排序,注意其中的坑

    参数签名中通常是按键值对中键名称的ASCII按从小到大的顺序排序后进行hash为签名字符串.不要直接使用 SortedDictionary<string, string> 有坑的,他是按数 ...

  6. centos部署yapi爬坑记

    前言 这几天终于完成了为期三个月的公司某个demo版的项目,在这期间和公司的后台因为API的事怼过无数次了,'我的接口没问题,是你请求的方式不对吧!'.'一定是你请求的参数不对'......诸如此类问 ...

  7. [转载][HASS.IO] 【HASSOS安装】成功安装HASSOS 1.9(避开了大部分坑版)

    7月20日HA官方放出HASSOS说明时,我开始入坑HASSOS,经历了安装没流量.打开主页:8123没显示.HASS.IO边栏不显示.安装不了HASS.IO插件等问题之后,在8月6日总算避开了大坑进 ...

  8. js粘贴事件paste简单解析及遇到的坑

    在用户执行粘贴操作的时候,js能够获得剪切板的内容,本文讨论一下这个问题. 目前只有Chrome支持获取剪切板中的图片数据.还好需要这个功能的产品目前只支持Chrome和Safari,一些Chrome ...

  9. iconfont采坑

    1. iconfont采坑 1.1. 前言 使用iconfont过程中踩过坑特此记录 不知道iconfont的这里也简单介绍一笔,阿里开放的一个图标素材库,用来快速找图标下载使用图标 iconfont ...

随机推荐

  1. formant

    1.函数功能将一个数值进行格式化显示. 2. 如果参数format_spec未提供,则和调用str(value)效果相同,转换成字符串格式化. >>> format(3.141593 ...

  2. springboot结合jsp页面详解

    第一次写博客,其实就是为了约束我自己,写的不一定对,互相借鉴吧!有不对的地方请多多指正,谢谢! 今天我们来看一下springboot结合jsp页面的具体操作: 1.首先我们先看一下目录结构 由上面我们 ...

  3. 完整的Socket代码

    先上图 列举一个通信协议 网关发送环境数据 此网关设备所对应的所有传感器参数,格式如下: 网关发送: 包长度+KEY值+请求类型+发送者+接收者+消息类型+消息内容 说明: 包长度:short int ...

  4. Python与CSV文件(CSV模块)

    Python与CSV文件(CSV模块)   1.CSV文件 CSV(逗号分隔值)格式是电子表格和数据库最常用的导入和导出格式.没有“CSV标准”,因此格式由许多读写的应用程序在操作上定义.缺乏标准意味 ...

  5. Oracle--listagg函数使用注意

    listagg是一个列转行函数,在一对多的情况下,通过该函数转换成一对一 该函数使用跟聚合函数中的SUM差不多,但需要注意的是,该函数不适合用于多表连接情况下,都则及时字段值相同也会出现多值 例如: ...

  6. es入门--curl的使用

    文档介绍: 首先要讲什么是文档,我们中大多是java程序员,java是面向对象的,那么在elasticsearch看来:对象和文档是等价的.只不过这个对象是可以被序列化成key-value形式的jso ...

  7. DVWA--XSS(stored)

    XSS 0X01 1.简介 跨站脚本(cross site script)为了避免与样式css混淆,所以简称为XSS. XSS是一种经常出现在web应用中的计算机安全漏洞,也是web中最主流的攻击方式 ...

  8. 大数据笔记(二十五)——Scala函数式编程

    ===================== Scala函数式编程 ======================== 一.Scala中的函数 (*) 函数是Scala中的头等公民,就和数字一样,可以在变 ...

  9. jQuery file upload cropper的流程

    https://tkvw.github.io/jQuery-File-Upload/basic-plus-editor.html 最开始初始化jquery.ui.widget.js中的factory( ...

  10. React Native商城项目实战07 - 设置“More”界面导航条

    1.More/More.js /** * 更多 */ import React, { Component } from 'react'; import { AppRegistry, StyleShee ...