ThreadLocal
本质上,ThreadLocal是通过空间来换取时间,从而实现每个线程当中都会有一个变量的副本,这样每个线程就都会操作该副本,从而完全
规避了多线程的并发问题。
Java中存在四种类型的引用:
- 强引用(strong)
- 软引用(soft)
- 弱引用(weak)
- 虚引用(phantom)
public class Test{
private static final ThreadLocal tl = new ThreadLocal();
}
try {
…
…
…
} finally {
tl.remove();
}
threadLocal
ThreadLocalMap其实就是ThreadLocal的一个静态内部类,维护了一个entry数组,继承了WeakReference里面定义了一个Entry来保存数据,
hash冲突:
初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,简单的步长加1或减1,寻找下一个相邻的位置。
内存泄漏:
threadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露
解决方法:在使用结束时,最好调用ThreadLocal.remove来释放其value的引用:确定元素的位置,找到Entry,把entry的键值对都设为null,最后也Entry也设置为null
ThreadLocal,即线程变量,是一个以 ThreadLocal 对象为键,任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal 对象查询到绑定在这个线程的一个值。
Thread 类中有一个成员变量属于 ThreadLocalMap 类(一个定义在 ThreadLocal 类中的内部类),它是一个 map,它的 key 是 ThreadLocal 实例对象。
当为 ThreadLocal 类的对象 set 值时,首先获取当前线程的 ThreadLocalMap 变量,然后以ThreadLocal 类的对象为 key,设定 value。get 值时则类似。ThreadLocal 变量的活动范围为某线程,是该线程“专有的,独自霸占”的,对该变量的所有操作均由该线程完成!也就是说,ThreadLocal 不是用来解决共享对象的多线程访问的竞争问题的,因为 ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。当线程终止后,这些值会作为垃圾回收。由 ThreadLocal 的工作原理决定了:每个线程独自拥有一个变量,并非是共享的。需要注意的是,每次 set/get 值,不直接用线程 id 来作为 ThreadLocalMap 的 key,因为若直接用线程 id 当作 key,无法区分放入 ThreadLocalMap 中的多个value。所以是使用 ThreadLocal 作为 key,因为每一个 ThreadLocal 对象都可 以由 threadLocalHashCode 属性(final 修饰,每次实例创建后就不会更改了) 唯一区分或者说每一个 ThreadLocal 对象都可以由这个对象的名字唯一区分,所以可以用不同的 ThreadLocal 作为 key,区分不同 value。
如何保证两个同时实例化的 ThreadLocal 对象有不同的 threadLocalHashCode 属性:在 ThreadLocal 类中,还包含了一个 static 修饰的 AtomicInteger(提供原子操作的 Integer 类)类变量(nextHashCode)和一个 static final 修饰的常量(作为两个相邻 nextHashCode 的差值)。由于 nextHashCode 是类变量,所以每一次创建 ThreadLocal 对象都可以保证 nextHashCode 被更新到新的值,并且下一次调用 ThreadLocal 类这个被更新的值仍然可用,同时 AtomicInteger 保证了 nextHashCode 自增的原子性。
TreadLocal 和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
- 在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
- 而 ThreadLocal 则从另一个角度来解决多线程的并发访问。ThreadLocal 会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进 ThreadLocal。
- 概括起来说,对于多线程资源共享的问题,同步机制采用了 “以时间换空间”的方式,而 ThreadLocal 采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
在默认单例的 Spring bean 中,将一些非线程安全的变量以 ThreadLocal 存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。需要注意的是,TheadLocalMap 作为 hash 表的一种实现方式,是通过开放寻址法来解决哈希冲突,这点不同于 HashMap。开放寻址法的核心是如何出现了散列冲突,就重新探测一个空闲位置,将其插入。当我们往散列表插入数据时,如果某个数据经过散列函数散列之后,存储位置已经被占用了,我们就从当前位置开始,以此往后查找,看是否有空闲位置,直到找到为止。另外,ThreadLocalMap 的初始长度为 16。当集合中 size 数量大于规定长度的
1/2()时,则执行 resize()操作,扩容到原来两倍。