前期两篇ThreadLocal相关文章,我们大概了解其运行原理。分别是ThreadLocal浅析、深入细节ThreadLocalMap,带着问题去学习,加深理解。
相关问题
1.为什么Entry key是弱引用,而value是强引用?
个人理解就是在保证value可使用的情况下,降低内存占用。
我们从源码中可以看出,虽然Entry中key,也就是ThreadLocal,是弱引用,但同时ThreadLocal也是被外部引用的,ThreadLocal既是Entry中的key,也是外部对象实例。
key是弱引用,他的价值体现只有在ThreadLocal外部对象引用计数为0的情况才能体现出来。举一个场景,一个线程中,多个ThreadLocal对象,在他们引用计数不为0的情况下,Entry中的key,也就是ThreadLocal,gc的情况下,是不会被回收的。只有你不想用其中某些ThreadLocal实例了,把他们设为null,这个时候gc,可以回收key。这个不难理解吧。
那为啥value不是弱引用?个人理解,可能你在设置某些ThreadLocal=null时,在后面继续使用该key对应的value,如果value也是弱引用,那也会被回收,数据会出问题。
有人会想,key被回收后,后面get取不到数据,导致数据丢失。我理解是,你都把ThreadLocal设为null了,你还用他?
2.ThreadLocal内存泄漏问题
通过 深入细节ThreadLocalMap 这篇文章,我们知道在set、get方法中都会清理ThreadLocalMap数组,所以正常情况下,不会存在内存泄漏问题。
但是在ThreadLocal在没有外部对象强引用时,发生GC会导致弱引用Key会被回收,后面又没有set、get操作,导致Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。所以当我们完成任务后,最好手动调用下remove方法。
3.ThreadLocalMap和HashMap有什么区别?
HashMap相关知识可以看下 HashMap之扩容 这篇文章文章。
1.结构不同
HashMap 的数据结构是数组 链表,ThreadLocalMap的数据结构仅仅是数组。
2.解决hash冲突方式不同
解决hash冲突,通常是两种方法:链表法和开放地址法。链表法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位;开放地址法是通过一个探测算法,当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位
HashMap解决hash冲突用的是链表法
新添加的 Entry 对象放入 table 数组的 bucketIndex 索引处。
如果 bucketIndex 索引处已经有了一个 Entry 对象,那新添加的 Entry 对象指向原有的 Entry 对象(产生一个 Entry 链)。
如果 bucketIndex 索引处没有 Entry 对象,也就是上面程序代码的 e 变量是 null,也就是新放入的 Entry 对象指向 null,也就是没有产生 Entry 链。
ThreadLocalMap解决hash冲突用的是开放地址法
和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有用链表的方式,而是采用线性探测的方式,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
ThreadLocalMap使用开放地址法原因
ThreadLocal 往往存放的数据量不会特别大,冲突概率低,单纯数组结构更省空间,同时数组的查询效率也是非常高。
还有其他关于ThreadLocal的问题,后面再继续加。