ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的。
ThreadLocal实例是被其创建的类持有(更顶端应该是被线程持有),而ThreadLocal的值是被线程实例持有,它们都是位于堆上,只是通过一些技巧将可见性修改成了线程可见。
ThreadLocal具体实现:每个线程对象内部有个ThreadLocalMap的成员变量threadLocals,它是一个map的结构,底层是Entry数组table
set操作:就以WeakReference包装ThreadLocal变量,即令其中的referent引用指向这个变量,并将这一个WeakReference作为key,要set的参数作为value,封装成一个Entry插入到map当中,如果出现hash冲突则通过线性再散列方法处理
get操作:首先从对应线程中拿到对应的threadLocals,也就是那个map,接着调用map.get方法,通过计算到的hash值和table.length-1相与,确定所在的位置并返回值。threadLocals允许有键为null的Entry存在
应用场景
1、Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以使业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复。主要通过TransactionSynchronizationManager这个类。
2、在Android中,Looper类就是利用了ThreadLocal的特性,保证每个线程只存在一个Looper对象。
3、当一个线程经常遇到横跨若干方法调用,需要传递=上下文(Context),比如用户身份、任务信息,存在过渡传参的问题。可使用ThreadLocal,在调用前在ThreadLocal中设置参数,其他地方通过ThreadLocal的get方法去获取。
4、ThreadLocal结合AOP,实现日志打印,每个线程负责的请求完成之后再一起打印,多个线程之间互不影响。
5、多个线程同时使用SimpleDateFormat的时候,可能线程不安全,因为要操作同一个pattern成员变量,这时就可以将SimpleDateFormat用ThreadLocal封装。
源码
set源码:
public void set(T value) {
Thread t = Thread.currentThread();// 获取当前线程
ThreadLocalMap map = getMap(t);// 获取ThreadLocalMap对象
if (map != null) // 校验对象是否为空
map.set(this, value); // 不为空set
else
createMap(t, value); // 为空创建一个map对象
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
通过ThreadLocalMap去判断当前线程是否存在有map对象,而map对象则是通过当前Thread的threadLocals的变量中获取的。
每个线程Thread都维护了自己的threadLocals变量,当每个线程创建ThreadLocal,获取的数据是存在自己线程Thread的threadLocals变量里面的,从而实现了隔离。
ThreadLocalMap 的Entry源码:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
......
private Entry[] table;
存数据的类对象继承了弱引用,类似HashMap,但没有next,不是链表结构,采用数组结构
ThreadLocalMap 的Set源码:
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
每一个ThreadLocal对象都有一个threadLocalHashCode,在插入过程中,根据这个hash值,定位到table中的位置i,如果当前位置是空的,就初始化一个Entry对象放在位置i上;如果不为空且key对象正好是传参的key对象,就刷新Entry中的value;
Thread init部分源码:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
.......
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
........
}
使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值。如果线程的inheritThreadLocals变量不为空,而且父线程的inheritThreadLocals也存在,那么就把父线程的inheritThreadLocals给当前线程的inheritThreadLocals。
ThreadLocal实现父子线程数据共享
InheritableThreadLocal可实现
1、在创建 InheritableThreadLocal 对象的时候赋值给线程的 t.inheritableThreadLocals 变量
2、在创建新线程的时候会 check 父线程中 t.inheritableThreadLocals 变量是否为 null,如果不为 null 则 copy (浅拷贝)一份数据到子线程的 t.inheritableThreadLocals 成员变量中去
3、InheritableThreadLocal 重写了 getMap(Thread) 方法,所以 get 的时候,就会从 t.inheritableThreadLocals 中拿到 ThreadLocalMap 对象,从而实现了可以拿到父线程 ThreadLocal 中的值
存在问题
内存泄漏
Entry的key被设计成了弱引用
ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
解决办法:在代码的最后使用remove即可;在使用的最后用remove把值清空就好了。
为何key设计成弱引用?
key不设置成弱引用的话就会造成和entry中value一样内存泄漏的场景。
是因为用强引用的话,如果使用ThreadLocal变量的某个线程长时间不中止,就算这个ThreadLocal对象超过了它应当的存在的生命周期,还是不能被回收,出现短生命周期对象被长生命周期引用所指向的情况,出现内存泄漏