本文主要记录最近在解决一个兄弟的代码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步,一定能有
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。