背景
最近又聊到了ThreadLocal关键字,然后就梳理一下其中内容。这次直接读源码,从源码角度来分析一下。ThreadLocal提供了一个线程私有的区域,线程可以自己放一下值进去,然后用的时候取出来。那么它是怎么实现的呢?
ThreadLocal
public class Main {
public static void main(String[] args) {
ThreadLocal<String>tLocal=new ThreadLocal<>();
tLocal.set("a");
System.out.println(tLocal.get());
}
}
比如这段程序,就基本展示了ThreadLocal怎么用的。从set点进去,看一下源码:
/**
* 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);
}
可以看到基本流程:
(1)首先找到当前的thread
(2)调用getMap,得到一个ThreadLocalMap对象
(3)如果map是空,新建一个,否则就set值进去。
这里继续看一下getMap的实现
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
getMap其实就是Thread对象的一个成员,说明Thread类里面有一个ThreadLocalMap的成员。点进去Thread类里面看一下:
在第182行确实定义了这个成员,但是这个成员是的类型是:ThreadLocal.ThreadLocalMap,说明其实ThreadLocalMap 是定义在ThreadLocal里面的。
到这里,我们基本搞那个明白了一个事实ThreadLocal 之所以做到了线程私有,其实是当前线程里面有一个ThreadLocalMap的类,数据都是被存在了这个类里面,并且这个类在Thread类里面默认是null,所以就有了开始set的那一段代码然后回来继续看一下ThreadLocal类
ThreadLocalMap
在Thread类里面可以看到ThreadLocalMap的定义是:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
这里直接用ThreadLocal访问了ThreadLocalMap类,点进去看一下:
可以发现,原来ThreadLocalMap类是ThreadLocal的一个静态内部类,这个类里面也有一个静态内部类:Entry,这个就是map的实体了。可以看到源码:
/**
* 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;
}
}
在加上之前set里面的代码:
可以发现,这个map的key是这个ThreadLocal的WeakReference,value是我们丢进来的值。
会出现的问题
在上面的分析中可以看出来,ThreadLocal存放变量的原理,其实就是存放在了Thread自己的ThreadLocalMap里面,并且key是ThreadLocal的一个弱引用。as we know,弱应用在gc的时候是一定会被回收的,如果这个时候value是一个强引用,那么map里面就会出现一个key为null的值,这是不符合实际的。这也就是ThreadLocal的内存泄漏问题。为了解决这个问题,其实ThreadLocal提供了一个remove方法,并且在get,set里面都会调用这个方法,及时清除掉key为null的对象。