转载请注明出处:http://blog.csdn.net/jevonscsdn/article/details/54353177 【Jevons’Blog】
ThreadLocal的目的和作用:
用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。
每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map
增加一条记录,key
就是ThreadLocal
对象,而value
就是各自线程通过set
方法传进去的值。在线程结束时,可以调用ThreadLocal.clear()
方法,这样会更快释放内存(非必调,因为线程结束后也可以自动释放相关的ThreadLocal
变量)。
当我们需要使用到线程本地变量时,常常会想到ThreadLocal类,而每一个线程都各自维护着一个ThreadLocal.ThreadLocalMap映射表:
public class Thread implements Runnable {
...
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals= null;
...
}
下面我们通过源代码分析Thread中持有变量threadLocals的初始化过程。
先来看一下初始化方法:
public class ThreadLocal<T> {
...
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
...
}
可以看出该 createMap(Thread t, T firstValue)
方法为Thread类中的threadLocals
进行了初始化操作。
再追踪一下ThreadLocalMap的构造方法源码:
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
size :The number of entries in the table.即为table里entry的数量;
setThreshold(int len):Set the resize threshold to maintain at worst a
2/3 load factor.设置容量调整阈值为2/3的len;
可以看出该构造方法初始化了Entry[] table
的初始容量:
private static final int INITIAL_CAPACITY = 16;
通过firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)
运算获得索引,再new 一个Entry(firstKey, firstValue)
,该Entry继承了WeakReference<ThreadLocal>
类,说明它持有的key即为对ThreadLocal的弱引用。下面来看一下源码:
static class ThreadLocalMap {
...
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
...
}
可以看到构造方法中调用了父类的构造并传入了key,再往下追踪可以追踪到WeakReference类:
public class WeakReference<T> extends Reference<T> {
/**
* Creates a new weak reference that refers to the given object. The new
* reference is not registered with any queue.
*
* @param referent object the new weak reference will refer to
*/
public WeakReference(T referent) {
super(referent);
}
...
}
再往下:
public abstract class Reference<T> {
...
/* -- Constructors -- */
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
...
}
这里的 Reference(T referent)
又调用了Reference(T referent, ReferenceQueue<? super T> queue)
并传入了ThreadLocal作为Key,这里非常关键,在我们后面通过ThreadLocal的get()方法中就涉及到,他是判断Key是否相同的关键。那这个referent是个什么东东,源码里是它的注释:
/* Treated specially by GC */
GC特别对待对象!这是一个被GC时刻关注的引用,有关弱引用这里就不详细介绍了,自行google~
好了坑挖的有点深,现在回过头,那么ThreadLocal类中这个createMap(Thread t, T firstValue)
方法到底被谁所调用呢?
在ThreadLocal中有两个方法对它进行了调用:
1.set(T)
2. T setInitialValue()
set(T)的源码:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
可以看到,该方法先通过Thread.currentThread();拿到当前线程,将它塞入getMap(t)获得一个ThreadLocalMap返回值,下面是getMap()的源码:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
简单粗暴的直接返回了thredLocals,然后进行判断,假如Map为空(不为空就不说了),就调用createMap(t, value)方法,将当前线程和需要设置的线程本地变量value传入,并对threadLocals 进行了初始化操作。
setInitialValue()源码:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
Bingo!同样的招数。
假如我并没有先设值而是直接get()会怎样呢?
get()源码:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
由于我没有设值,所以map 为空,直接回调setInitialValue();
以上为ThreadLocal.ThreadLocalMap映射表的初始化过程,下面介绍ThreadLocal的get()方法运作过程。
接上所述,若事先有设值,且map不为空,通过map.getEntry(this)将ThreadLocal对象作为key传入。
ThreadLocalMap的getEntry方法的源码:
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
先通过 key.threadLocalHashCode & (table.length - 1) 运算得到Entry的索引 i,再通过table[i]得到Entry e进行判断,如果e不为空,则通过e.get()拿出entry里面的key和传入的key比较,若相等,直接返回e,否则回调getEntryAfterMiss(key, i, e)。
我们先来看这个e.get()
的源码:
/**
* Returns this reference object's referent. If this reference object has
* been cleared, either by the program or by the garbage collector, then
* this method returns <code>null</code>.
*
* @return The object to which this reference refers, or
* <code>null</code> if this reference object has been cleared
*/
public T get() {
return this.referent;
}
又是这个referent
,这么巧又见面了,用蹩脚英语翻译一下,源码注释的意思为:
返回这个引用对象的引用,如果这个引用对象已经被程序或者垃圾回收器清除了,就返回null值。
@return 这个应用对象的引用,如果这个引用对象已经被清除,则返回null值。
So,这个referent
拿到的就是当初设进去的key
,这里又通过e.get()
拿出来当作比较条件。上面说过条件不成立的话则回调函数getEntryAfterMiss(key, i, e)
,什么情况下会导致条件不成立呢?继续追踪源码:
/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
我们看一下源码注释:
*Version of getEntry method for use when key is not found in its
direct hash slot.
意思就是用于当key无法通过直接哈希片获得时的解决方案,什么情况下会丢失掉key呢?这里有三个判断点,第一个就不用说了,第二个的我们最后说,至于第三个我们来看一下源码中对i参数的解释:
@param i the table index for key’s hash code 用于获取Key的哈希码的table索引
那么不难猜出,位置不对,通过nextIndex(i, len)
进行下一个索引追查。
我们来说说第二种情况,之前我们也说到Entry
的key
是对ThreadLocal
的弱引用,而而弱引用是较容易被GC回收的,容易到什么程度呢?下一次GC工作时就会把他回收掉,所以就会出现Entry
里面的key
为空null
情况,显然人家也帮你考虑到了这些事情,因此为了防止内存泄露,当识别到key为null时,就会调用expungeStaleEntry(i)
把该Entry清除掉。