什么是ThreadLocal
ThreadLocal类顾名思义可以理解为线程本地变量。也就是说如果定义了一个ThreadLocal,
每个线程往这个ThreadLocal中读写是线程隔离,互相之间不会影响的。它提供了一种将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制。
常见应用场景
1.DAO的数据库连接,动态数据源切换
2.SimpleDataFormat解决线程不安全问题
问题:SimpleDataFormat的parse()方法,内部有一个Calendar对象,调用SimpleDataFormat的parse()方法会先调用Calendar.clear(),然后调用Calendar.add(),如果一个线程先调用了add()然后另一个线程又调用了clear(),这时候parse()方法解析的时间就不对了。
方案:ThreadLocal包装SimpleDataFormat,再调用initialValue让每个线程有一个SimpleDataFormat的副本,从而解决了线程安全的问题,也提高了性能。
不过现在java8提供了DateTimeFormatter它是线程安全
3.用 ThreadLocal 保存一些业务内容
(用户权限信息、从用户系统获取到的用户名、用户ID 等),这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的。
ThreadLocal方法
set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法还是比较简单的,我们可以重点看下这个方法里面的ThreadLocalMap,它既然是个map(注意不要与java.util.map混为一谈,这里指的是概念上的map),肯定是有自己的key和value组成,我们根据源码可以看出它的key是其实可以把它简单看成是ThreadLocal,但是实际上ThreadLocal中存放的是ThreadLocal的弱引用,而它的value的话是我们实际set的值。
常见问题
为什么需要数组呢?没有了链表怎么解决Hash冲突呢?
用数组是因为,我们开发过程中可以一个线程可以有多个TreadLocal来存放不同类型的对象的,但是他们都将放到你当前线程的ThreadLocalMap里,所以肯定要数组来存。
ThreadLocalMap采用「线性探测」的方式,什么是线性探测呢?就是根「据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置」。ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。
如果一个线程里面有大量的ThreadLocal就会产生性能问题,因为每次都需要对这个table进行遍历,清空无效的值。所以我们在使用的时候尽可能的使用少的ThreadLocal,不要在线程里面创建大量的ThreadLocal,如果需要设置不同的参数类型我们可以通过ThreadLocal来存放一个Object的Map这样的话,可以大大减少创建ThreadLocal的数量。
ThreadLocal的内存泄露
当ThreadLocal没有被外部强引用的时候就会被GC回收(threadLocal = null):ThreadLocalMap会出现一个key为null的Entry,但这个Entry的value将永远没办法被访问到。如果当这个线程一直没有结束,那这个key为null的Entry因为也存在强引用,而Entry被当前线程的ThreadLocalMap强引用(Entry[] table),导致这个Entry.value永远无法被GC,造成内存泄漏。
针对于这种情况 ThreadLocalMap在设计中,已经考虑到这种情况的发生,你只要调用了set()、get()、remove()方法都会调用cleanSomeSlots()、expungeStaleEntry()方法去清除key为null的value。这是一种被动的清理方式,但是如果ThreadLocal的set(),get(),remove()方法没有被调用,就会导致value的内存泄漏。
父子之间线程的变量传递丢失
InheritableThreadLocal提供了一种父子线程之间的数据共享机制。
小结
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。