关于ThreadLocal
JDK1.2的版本中就提供java.lang.ThreadLocal类,每一个ThreadLocal能够放一个线程级别的变量, 它本身能够被多个线程共享使用,并且又能够达到线程安全的目的,且绝对线程安全。也就是说,ThreadLocal是保存当前线程的变量,当前线程内,可以任意获取,但每个线程往ThreadLocal中读写数据是线程隔离,互不影响。
ThreadLocal包含了四个方法:
void set(Object value) // 设置当前线程的线程局部变量的值。
public Object get() // 该方法返回当前线程所对应的线程局部变量。
public void remove()// 将当前线程局部变量的值删除,其目的是为了减少内存使用,加快内存回收。
protected Object initialValue()// 返回该线程局部变量的初始值,该方法是一个protected的方法,目的是为了让子类覆盖而设计的。
原理
下面我们开始阅读源码,从源码中找出答案。
- 首先,我们先看下ThreadLocal类中的set方法和getMap方法。
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
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;
}
我们可以发现调用ThreadLocal的set方法时,传入的参数value会存入到一个ThreadLocalMap对象中。接着,我们找找ThreadLocalMap是从哪里来的,通过getMap方法,我们也不难发现。ThreadLocalMap对象,就是当前线程的一个成员变量threadLocals。
总结,也就是说,每次我们每次往ThreadLocal中set值就是存入了当前线程对象的threadLocals属性里,而threadLocals的类型是ThreadLocalMap。ThreadLocalMap 可以理解为 ThreadLocal 类实现的定制化的 HashMap。
- 然后我们再看下ThreadLocal的get方法源码。
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
同set方法一样,也是先根据当前线程获取ThreadLocalMap对象,然后在map中取值。
总结
ThreadLocal 是实现变量间的共享与线程家呢隔离是靠ThreadLocalMap对象实现的,因为每次会拿到当前线程的ThreadLocalMap,每次往ThreadLocal 中设置值,是互相不影响的,因此可以实现线程间的隔离。
内存泄漏问题
ThreadLocal还有一个众所周知的问题,就内存泄漏。首先我们要知道什么是内存泄漏:内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
ThreadLocal存在内存泄漏问题,可以理解成在虚拟机垃圾回收时,会出现ThreadLocal占用内存无法被GC回收。这里说一下弱引用:在GC的时候,不管内存空间足不足都会回收这个对象。为什么呢? 下面上源码:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
可以看到,ThreadLocalMap 中使⽤的 key为ThreadLocal 的弱引⽤(WeakReference)。ThreadLocalMap中的Entry对象继承了WeakReference弱引用类,并且在Entry的构造方法中,会以key作为参数传入到父类构造方法”
这时候出现一个问题:ThreadLocalMap 中使⽤的 key是以弱引用指向ThreadLocal,这时候垃圾回收器线程运行,发现弱引用就回收,key被回收。ThreadLocalMap里对应的Entry的key会变成null。这时候尴尬出现了,ThreadLocalMap里对应的Entry的value则无法被访问到,value作为一个强引用垃圾回收不到也不能被访问,即造成了内存溢出。
解决办法
想要解决这个内存溢出问题的也不难,需要我们养成好的代码习惯,在我们不使用的时候,主动调用remove方法进行清理。