ThreadLocal可以理解为线程局部变量,当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本,ThreadLocal保证了各个线程的数据互不干扰。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
每个线程中都有一个ThreadLocalMap
数据结构,当执行set方法时,其值是保存在当前线程的threadLocals
变量中,当执行get方法中,是从当前线程的threadLocals
变量获取。
也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。值得注意的是图中的虚线,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。
在ThreadLoalMap中,也是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象。通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。
ThreadLoalMap的Entry是继承WeakReference,和HashMap很大的区别是,Entry中没有next字段,所以就不存在链表的情况了
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
每个ThreadLocal对象都有一个hash值threadLocalHashCode
,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小0x61c88647
。
在插入过程中,根据ThreadLocal对象的hash值,定位到table中的位置i,过程如下:
这样的话,在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,然后判断该位置Entry对象中的key是否和get的key一致,如果不一致,就判断下一个位置
可以发现,set和get如果冲突严重的话,效率很低,因为ThreadLoalMap是Thread的一个属性,所以即使在自己的代码中控制了设置的元素个数,但还是不能控制其它代码的行为。
当使用ThreadLocal保存一个value时,会在ThreadLocalMap中的数组插入一个Entry对象,在ThreadLocalMap的实现中,key被保存到了WeakReference对象中。这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
解决:
在调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就可以被回收,当然如果调用remove方法,肯定会删除对应的Entry对象。
但是这些被动的预防措施并不能保证不会内存泄漏:
考虑两种情况:
由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
synchronized:以时间换空间,一个变量,让不同线程排队访问;
ThreadLocal:空间换时间,多个变量,多个线程之间同时访问不受影响。
ThreadLocal建议使用static修饰,这个变量是针对一个线程内所有操作共享的,所以设置成静态的,所有此类实例共享此静态变量,也就是说在类加载时分配一块存储空间,所有此类的对象都可以操作这个变量。
主要用于一些connection,session资源的管理,比如SqlSession
sqlsession管理
spring 事务管理 TransactionSynchronizationManager。用ThreadLocal存储Connection,从而各个DAO可以获取同一个Connection,可以进行事务回滚,提交等操作。
有测试说FastThreadLocal是ThreadLocal 3倍的速率;
FastThreadLocal是Netty提供的,在池化内存分配的地方都有涉及到。
FastThreadLocal用法上兼容ThreadLocal,
使用FastThreadLocal居然不用像ThreadLocal那样先try ………………… 之后finally进行threadLocal对象.remove();
由于构造FastThreadLocalThread的时候,通过FastThreadLocalRunnable对Runnable对象进行了包装:
FastThreadLocalRunnable在执行完之后都会调用FastThreadLocal.removeAll();
FastThreadLocal操作元素的时候,使用常量下标在数组中进行定位元素来替代ThreadLocal通过哈希和哈希表,这个改动特别在频繁使用的时候,效果更加显著!
想要利用上面的特征,线程必须是FastThreadLocalThread或者其子类,默认DefaultThreadFactory都是使用FastThreadLocalThread的
只用在FastThreadLocalThread或者子类的线程使用FastThreadLocal才会更快,因为FastThreadLocalThread 定义了属性threadLocalMap类型是InternalThreadLocalMap。如果普通线程会借助ThreadLocal。