乐趣区

关于nio:线程安全

本文次要记录最近在解决一个兄弟的代码 bug 中的几点比较突出的问题.

多线程注意事项
1、HashMap 不是线程平安的

 在应用多线程的时候 , 肯定要留神本人所应用和设计的数据结构是否是线程平安的.
比方 Java 中平时用的最多的 Map 汇合就是 HashMap 了,它是线程不平安的
为了避免出现线程平安的问题,不能应用 HashMap 作为成员变量,要寻求应用线程平安的 Map 如 HashTable、SynchronizedMap、ConcurrentHashMap ; 当然, 也能够本人写锁来管制 hashMap 的平安. eg: 读写锁 

如下, 是咱们现成的我的项目中的代码,多线程的参数传递,应用了 HashMap, 导致了很大的生产事变

2、多线程在进行对象的拷贝时, 务必应用深拷贝模式

 存在对象时 , 倡议应用序列化对象的形式进行深拷贝 . 因为对象的数据结构较为简单 , hashMap 的 putAll() 甚至是 apache 开源的 CloneUtils.clone() 都不可能完全正确的进行深拷贝.

2.1 此处,插播一个问题, 不晓得大家留神到没有, 下面的代码中,HashMap 在进行初始化的时候,并没有指定 map 的大小,这里也就出了一个新的问题: 服务始终在占用高 CPU

HashMap 在并发的环境下进行 rehash 的时候会造成链表的闭环,因而在进行 get() 操作的时候导致了 CPU 占用 100% . 
为什么进行 rehash ?  具体去看源码 , 大略就是 map 的大小触碰到了 HashMap 的阈值 threshold(map 实现的时候就有了), 当 HashMap 的容量达到  threshold    时就须要进行扩容,这个时候就要进行 ReHash 操作了,能够看 addEntry 函数的实现,当 size 达到 threshold 时会调用 resize 进行扩容 

2.2 在排查 CPU 占用的问题的时候,咱们定位到一个 jdk 的 bug : Selector BUG
上面咱们对这个进行一个解说

2.2.1 Selector BUG 呈现的起因
     Selector 的轮询后果为空,也没有 wakeup 或新音讯解决,则产生空轮询,CPU 使用率 100%,(因为 selector 的 select 办法,返回 numKeys 是 0,所以上面本应该对 key 值进行遍历的事件处理基本执行不了,又回到最下面的 while(true) 循环,周而复始,一直的轮询,直到 linux 零碎呈现 100% 的 CPU 状况,其它执行工作干不了活 )

2.2.2 Netty 的解决办法
    对 Selector 的 select 操作周期进行统计,每实现一次空的 select 操作进行一次计数
    若在某个周期内间断产生 N(默认是 512)次空轮询,则触发了 epoll 死循环 bug
    重建 Selector,判断是否是其余线程发动的重建申请,若不是则将原 SocketChannel 从旧的 Selector 上去除注册,从新注册到新的 Selector 上,并将原来的 Selector 敞开。2.2.3 该 bug 相干信息
  https://bugs.java.com/bugdatabase/view_bug.do?bug_id=2147719
  https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6403933
  这个 bug 的形容内容为,在 NIO 的 selector 中,即便是关注的 select 轮询事件的 key 为 0 的话,NIO 照样一直的从 select 本应该阻塞的状况中 wake up 出.

3、发现 Cpu 打满 常用命令 (三板斧)

1、top 命令,按 P 依照 CPU 使用率排序找到占用率最高的过程 pid
2、top -Hp pid 命令找到占用率最高的线程 tid
3、printf "%x\n" tid 命令把十进制线程 tid 转化为十六进制
4、jstack -l tid > stack.txt 打印栈信息
5、用十六进制的 tid 在堆栈信息中搜寻
6、如果没有搜寻到,反复执行几遍第 4 步,肯定能有

本文参考: https://pdai.tech/md/java/io/…

退出移动版