部分内容来自黑马和javaguide,有版权问题请联系我
ThreadLocal
ThreadLocal对象可以提供线程局部变量,每个线程Thread拥有一份自己的副本变量,多个线程互不干扰。
数据结构
Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。
ThreadLocalMap有自己的独立实现,可以简单地将它的key视作ThreadLocal,value为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。
每个线程在往ThreadLocal里放值的时候,都会往自己的ThreadLocalMap里存,读也是以ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
ThreadLocalMap有点类似HashMap的结构,只是HashMap是由数组+链表实现的,而ThreadLocalMap中并没有链表结构。
我们还要注意Entry, 它的key是ThreadLocal<?> k ,继承自WeakReference, 也就是我们常说的弱引用类型。
注意:当Thread销毁时,ThreadLocalMap也会销毁,减少内存的使用。
内存泄漏
Java的四种引用类型:
- 强引用:我们常常new出来的对象就是强引用类型,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足的时候
- 软引用:使用SoftReference修饰的对象被称为软引用,软引用指向的对象在内存要溢出的时候被回收
- 弱引用:使用WeakReference修饰的对象被称为弱引用,只要发生垃圾回收,若这个对象只被弱引用指向,那么就会被回收
- 虚引用:虚引用是最弱的引用,在 Java 中使用 PhantomReference 进行定义。虚引用中唯一的作用就是用队列接收对象即将死亡的通知
如果ThreadLocal使用强引用的话:
可以看到,ThreadLocal类无法被回收,造成内存泄漏。
为什么使用弱引用?
ThreadLocal可以被回收,但是Entry中的value仍然无法被回收,仍旧会导致内存泄漏。
原因与解决:
- 需要手动删除Entry,释放value中的内存。使用完ThreadLocal及时调用Remove()
- 当前线程存在则ThreadLocalMap也不会被回收。
事实上,在ThreadLocalMap中的set/getEntry方法中,会对Key值为null进行判断,如果Key为null,会对value置null,相当于释放value空间。所以实际上弱引用比强引用多一层保障,即使不不手动调用remove方法回收Entry空间,下一次调用set,get中的任一方法时都会回收value 的空间,从而避免内存泄漏。
Hash算法
int i = key.threadLocalHashCode & (len-1);
ThreadLocalMap中hash算法很简单,这里i就是当前key在散列表中对应的数组下标位置。
这里最关键的就是threadLocalHashCode值的计算,ThreadLocal中有一个属性为HASH_INCREMENT = 0x61c88647,每当创建一个ThreadLocal对象,这个ThreadLocal.nextHashCode 这个值就会增长 0x61c88647 。这个值很特殊,它是斐波那契数 也叫 黄金分割数。hash增量为 这个数字,带来的好处就是 hash 分布非常均匀。