作者:Ever_00

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

背景

上周有小伙伴反馈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 -> { // #_1switch (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: RUNNABLEat 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;}

正文结束

推荐阅读 ↓↓↓

1.

2.

3.

4.

5.

6.

7.

8.

一个人学习、工作很迷茫?

点击「阅读原文」加入我们的小圈子!

一个Lambda引发的血案相关推荐

  1. 『转』度百死去飞秋一个BUG引发的血案

    作了一篇文章度百死去飞秋一个BUG引发的血案,昨天,度百死去的美国客户发邮件给我,说我的软件出问题了,我查来查去,发现居然是服务器上一个目录无法删除,一删除就报 cannot read from th ...

  2. 【Elasticsearch】es 一个数据精度引发的血案

    1.概述 在看博客的时候,发现有个博客 一个数据精度引发的血案 上面说,数据精度会丢失,然后我在 7.8.0版本的es测试,发现数据没有丢失. POST /index-lcc-0002/_doc {& ...

  3. ”一个馒头引发的血案“|记Mybatis之BindingException异常的产生及解决过程

    一. 业务场景 前几天壹哥带学生做一个项目,需要更新数据库中的车辆信息表,具体需求是要根据指定车辆的设备id(编号和设备ID均非主键)来更新车辆信息.壹哥要求学生们用Mybatis进行实现,所以就在对 ...

  4. 一个馒头引发的血案...请看完无极后观看此片,保笑死人不偿命

    一个馒头引发的血案... 采用搞笑的手法拍摄的,笑到你喷饭,极大的讽刺无极 下载地址:点击下载

  5. 摘要: Druid连接池一个设置引发的血案 -- 链接池出现问题

    摘要: Druid连接池一个设置引发的血案 今天在一台配置很低的机器上运行批量更新的程序~~~ 大概跑了三十分钟~~~这配置~~~这程序~~~ 然后华丽丽的报异常了~~~ 具体异常是这样的, DEBU ...

  6. 一个随机数引发的血案

    一个随机数引发的血案 我也来做一次标题党 原文地址:http://blog.csdn.net/WinsenJiansbomber/article/details/50604653 目录 一个随机数引发 ...

  7. 线上 CPU100% 异常案例:一个正则表达式引发的血案

    前几天线上一个项目监控信息突然报告异常,上到机器上后查看相关资源的使用情况,发现 CPU 利用率将近 100%.通过 Java 自带的线程 Dump 工具,我们导出了出问题的堆栈信息. 我们可以看到所 ...

  8. 一个NODE_ENV 引发的血案

    1 表象 控制台报错 截图没有完整的截下来,其实右边行号并没有具体的行号, 显示为payment-809e8ff.js 1 很明显 这是 js语法错误 但是当点击开里面显示的是html内容 第一行的 ...

  9. 一个“alert” 引发的血案

    0 在还没有掌握全部证据之前就下结论会犯严重的错误,会使判断带有偏见.--<血字的研究> "齐识,路老板又来邮件了."白娜一脸无耐地说. "一定没好事吧?&q ...

  10. 一个域名引发的血案……

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由腾讯游戏云 发表于云+社区专栏 你在沙发上看世界杯,黑客在做什么? 深夜,当电视机屏幕上那个小小的足球牵动着亿万人的心弦时,猜一猜黑客 ...

最新文章

  1. Linux命令之 —— grep \ls \ ll \ sed \ bg fg \ ipset \ wc \ ifconfig \ awk
  2. 与Netflix合作 美电视运营商推出4K频道
  3. 6/100. Invert Binary Tree
  4. java中find方法_Java Document.find方法代码示例
  5. codeforces 122A-C语言解题报告
  6. 只能是做的HTML5审批流项目
  7. mysql error 1130 hy000:Host ‘localhost‘ is not allowed to connect to this mysql server 解决方案
  8. Access安全性之QA详解
  9. 怎么将tflite部署在安卓上_手把手教程:如何从零开始训练 TF 模型并在安卓系统上运行...
  10. Win下Eclipse提交hadoop程序出错:org.apache.hadoop.security.AccessControlException: Permission denied: user=
  11. 如何下载飞思卡尔单片机的S19文件
  12. 黑科技神器-uTools,必须下载
  13. 可能是求质数最高效的算法
  14. selenium点击爬取豆瓣高分电影
  15. blowfish java_blowfish加密算法
  16. 智慧城市不是建设出来的,而是运营出来的
  17. Scout - 可扩展的服务器和应用监控服务
  18. 光谱分布、光谱辐射通量密度与不同时间段分布光谱(图示)
  19. Broadcast 和 BroadcastReceiver
  20. 绝对的内幕!近些年各大IT公司售前薪水

热门文章

  1. Oracle将Java EE移交Eclipse基金会
  2. 利用Docker快速部署Oracle环境
  3. win7下安装cygwin工具
  4. 在CentOS上安装7zip和使用
  5. MacOS自动操作Automator的技巧
  6. 使用Blocs For Mac发布网站的方法
  7. 关于集合addAll()方法的坑度
  8. MySQL 8支持文档存储,并带来性能和安全方面的改进
  9. micro-mvc与主流mvc整合说明
  10. tomcat7简单优化