java并发之ThreadLocal

部分内容来自黑马和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, 也就是我们常说的弱引用类型。
java并发之ThreadLocal
注意:当Thread销毁时,ThreadLocalMap也会销毁,减少内存的使用。

内存泄漏

Java的四种引用类型:

  • 强引用:我们常常new出来的对象就是强引用类型,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足的时候
  • 软引用:使用SoftReference修饰的对象被称为软引用,软引用指向的对象在内存要溢出的时候被回收
  • 弱引用:使用WeakReference修饰的对象被称为弱引用,只要发生垃圾回收,若这个对象只被弱引用指向,那么就会被回收
  • 虚引用:虚引用是最弱的引用,在 Java 中使用 PhantomReference 进行定义。虚引用中唯一的作用就是用队列接收对象即将死亡的通知

如果ThreadLocal使用强引用的话:
java并发之ThreadLocal
可以看到,ThreadLocal类无法被回收,造成内存泄漏。

为什么使用弱引用?
java并发之ThreadLocal
ThreadLocal可以被回收,但是Entry中的value仍然无法被回收,仍旧会导致内存泄漏。

原因与解决:

  1. 需要手动删除Entry,释放value中的内存。使用完ThreadLocal及时调用Remove()
  2. 当前线程存在则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 分布非常均匀。

上一篇:ThreadLocal使用及原理


下一篇:农民工看完都学会了!java中间件开发