一.谈谈你对ThreadLocal的理解??
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
ThreadLocal为我们提供了线程局部变量,访问一个变量的每个线程都有该变量的本地副本,从而实现线程间的隔离,适用于多线程的情况.可以避免线程安全问题
二.ThreadLocal的应用场景
(1)SpringMVC中获取HttpServletRequest对象
javaweb中请求的执行流程:
springMVC会把HttpServletRequest放在ThreadLocal中,然后处理器方法就可以从中获取
(2)Spring的@Transational注解
当方法签到调用时,比如A方法调用B方法,B调用C…此时在A上加@Transational注解,并且A,B,C方法都要对数据库进行操作,A.B.C方法要使用同一个Connection连接对象时,就可以把连接对象设置在ThreadLocal中
(3)Mybatis分页插件也使用到ThreadLocal
三.ThreadLocal的核心源码
set方法
ThreadLocal.java
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
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();
}
Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal中有一个静态内部类ThreadLocalMap,
1.调用ThreadLocal的set方法时,首先会获取当前线程,并通过当前线程获取到ThreadLocalMap
2.然后把当前线程以及参数T传递给ThreadLocalMap的set方法
3.ThreadLocalMap底层使用的Entry静态内部类来存储数据
注意:
(1)每一个线程都拥有一个ThreadLocalMap threadLocals来保存本地变量
(2)一个线程可以有多个ThreadLocal,这也是ThreadLocal中拥有Entry[] table数组类的原因
(3)set方法底层其实是new了一个Entry(ThreadLocal threadLocal,T value)
get方法
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();
}
ThreadLocal的get方法,底层是获取Entry的value属性值
remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
四.ThreadLocal引发的内存泄露问题及解决
当我们不使用ThreadLocal时,如果
仅仅只是使ThreadLocal=null,那么会引发内存泄露问题!!!解决办法就是在不使用ThreadLocal时一定要remove
问题1:为什么Entry要继承弱引用WeakReference???
我们反证,如果Entry就是一个强引用,即使threadLcoal=null时,此时并不能释放ThreadLocal在堆空间占用的内存,因为此时Entry还引用了ThreadLocal(可达性分析),那么此时就会造成内存泄露;
相反,如果Entry继承了软引用,那么在new Entry时.会调用父类WeakReference的构造方法,把threadLocal传给WeakReference,此时如果把
threadLcoal=null,那么当垃圾回收器回收时,就会把ThreadLocal回收掉,因为它此时只被软引用所指向
问题2:Entry要继承弱引用WeakReference是否内存泄露问题就已经解决??
通过问题1得知,在令threadLocal=null时,垃圾回收器会把ThreadLocal内存回收掉,此时Entry的键也会为空,
那么该Entry的值也无法获取,也无法回收,于是也造成内存泄露;
所以为了解决内存泄露,需要我们使用完ThreadLocal后,手动remove删除;
问题3:阅读源码发现map在每次set之前都会清空key为null的entry,那我们还有必要手动remove吗??
有必要,因为有些线程是很长时间持续工作,在此期间不会执行set操作,那么便不会删除不再使用的ThreadLocal,从而造成内存泄露
问题4:拓展,如果不remove掉,除了造成内存泄露,还会引发什么问题??
如果我们是利用线程池创建的线程,那么在线程使用完成后,线程还会继续使用,如果此时ThreadLocalMap中的数据没有清空,那么该线程被再次使用时,会造成数据的错乱