整理出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表达式转换成所在类的一个方法lambdamethod{method}method{seq}(method为该 lambda 所在的方法名,例如上面的connect方法),同时通过动态代理生成一个代理类(该代理类实现了lambda表达式所代表的具体接口),在该代理类中调用lambdamethod{method}method{seq}。

在上面的例子中,生成的代理类大概如下:

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

业务线程:

  • 通过静态方法ClientZkAgent.getInstance()获取实例,第一次访问的时候会触发类ClientZkAgent的装载。
  • 装载过程中,装载静态成员instance,这时候会尝试创建一个ClientZkAgent对象。
  • 在ClientZkAgent的构造函数中连接zk,并通过CountdownLatch进入阻塞状态。注意这时候类装载还没完成。
  • CountdownLatch超时后完成对象的初始化以及整个类的加载。

zk事件线程:

  • SyncConnected事件触发后,调用ClientZkAgent.lambda$connect$0(event), 试图唤醒业务线程(唤醒逻辑在lambda中)。
  • 然而这时候ClientZkAgent还没加载完,事件线程只能等待类加载流程的结束。
  • 业务线程加载完ClientZkAgent后,事件线程完成事件的处理。
    可见,在这个过程中,两个线程相互等待(类似死锁但不是死锁),直至业务线程超时后才化解这个局面。

解决方法:

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

jdk lambda表达式的坑相关推荐

  1. 关于JDK lambda表达式与匿名内部类的等价实现却出现了截然不同的结果原因分析

    发现了一个很奇怪的现象,先上代码: public interface A {int f();default A cf(A other){return new A(){@Overridepublic i ...

  2. 【Java】jdk 1.8 新特性——Lambda表达式

    Lambda表达式 jdk 1.8 新加入的特性,简化了简单接口的实现 函数式接口 函数式中只有一个待实现的方法,可以使用@FunctionalInterface注解标注函数式接口.这个接口中只能有一 ...

  3. java jdk 1.8中lambda表达式常用方法

    在平常的开发工作当中,经常需要对数组进行一些操作,比如根据某个属性值分组,取出某个属性值作为数组等.那么,jdk 1.8为我们提供了便捷的方法,我们应该怎么使用呢? 1:filter:根据某个属性值过 ...

  4. JDK 8的新特性-Lambda表达式 精品文章总结

    文章目录 一. 前言 1.2 认识Lambda表达式 二. Lambda 表达式的格式 2.1 语法格式一: 无参数,无返回值,Lambda体只有一条语句 2.2 语法格式二: 有一个参数,并且无返回 ...

  5. jdk 8 中 Lambda 表达式练习题(经典面试题)

    jdk 8 中 Lambda 表达式练习题(经典面试题) 题1 调用 Collection.sort()方法,通过定制排序比较两个Employee(先按年龄,年龄相同按姓名比). 题2 ①声明函数式接 ...

  6. Java笔记整理五(Iterator接口,泛型,常见数据结构(栈,队列,数组,链表,红黑树,集合),jdk新特性,异常,多线程,Lambda表达式)

    Java笔记整理五 1.1Iterator接口 Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象 ...

  7. JDK 8 新特性 之 Lambda表达式

    前言 Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性. Lambda 允许把函数作为参数传递进方法中. 使用 Lambda 表达式可以使代码变的更加简洁紧凑. lamb ...

  8. Lambda表达式入门,看这篇就够了!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:海向 cnblogs.com/haixiang/p/1102 ...

  9. java函数式编程_Java 函数式编程和 lambda 表达式详解

    作者:DemonsI my.oschina.net/demons99/blog/2223079 为什么要使用函数式编程 函数式编程更多时候是一种编程的思维方式,是种方法论.函数式与命令式编程的区别主要 ...

  10. 10个Java 8 Lambda表达式经典示例

    Java 8 刚于几周前发布,日期是2014年3月18日,这次开创性的发布在Java社区引发了不少讨论,并让大家感到激动.特性之一便是随同发布的lambda表 达式,它将允许我们将行为传到函数里.在J ...

最新文章

  1. 新手必看,17 个常见的 Python 运行时错误
  2. ​谷歌大神Jeff Dean领衔,万字展望5大AI趋势
  3. 立刻停止使用AUFS,开启Overlay!
  4. CVS/SVN 托管服务
  5. C++ Primer 5th笔记(7)chapter7 类
  6. /dev 设备文件属性解读
  7. oracle11g ora 29927,Oracle11gR2使用RMANDuplicate复制数据库
  8. 经典面试题(28):以下代码将输出的结果是什么?
  9. php验证码显示碎图片,我的验证码只显示破碎的小图片
  10. 五大维度深掘工业互联网数据价值
  11. Ctrix-XenApp中误删应用服务器,如何重新添加
  12. 随机梯度下降算法(SGD)
  13. 数据库原理与应用实验3--(数据库的简单查询和连接查询)
  14. 如何在线下载哔哩哔哩上的视频
  15. codeblocks 编译器设置方法 也可以酱紫滴
  16. 雪花飘落代码java_JavaScript实现雪花飘落效果
  17. python+opencv实现相似图片的搜索
  18. 用angular JS和 bootstrap完成一个简单的购物车界面
  19. SNS网店软文推广法
  20. word研究报告排版要领

热门文章

  1. 大数据学情分析_大数据时代|如何轻松做好学情分析
  2. 谷粒商城:18.性能压测
  3. mysql函数commit_phpmysqli_commit()函数和mysqli_autocommit()函数比较
  4. 华为算法精英赛(题3:概率计算)
  5. 随手记_科研攻略_好的idea的产生
  6. 目标检测(8):CenterNet-Objects as Points-将目标建模为边界框中心点的方法
  7. 人工智能原理知识点对照
  8. 人工智能领域有哪些曾被拒稿的优秀工作?
  9. window.onload和$(document).ready()比较
  10. 《实时控制软件设计》团队项目第三天工作日志