下面分析基于JDK1.8。首先来看一个简单的ThreadLocal使用的例子。
public class Test {
ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
}
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
public static void main(String[] args) throws InterruptedException {
final Test test = new Test();
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
Thread thread1 = new Thread() {
public void run() {
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
};
};
thread1.start();
thread1.join();
System.out.println(test.getLong());
System.out.println(test.getString());
}
}
上述代码输出:
1
main
11
Thread-0
1
main
get的代码非常简单。我们可以看到是维护了一个map。这个map的key是该threadlocal对象。你需要通过当前线程对象取到这个map。
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();
}
这个map存储在当前线程对象中。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//thread类中的代码。
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap是定义在ThreadLocal中的静态内部类。这个Map没有使用Hashmap,而是该类作者自己实现的。首先是Entry类的实现,Entry类继承了WeakReference类。
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;
}
}
这是因为这个map是保存在线程对象里的。如果不加处理,只要线程对象不消失,这些key和value就会永远存在下去!使用WeakReference,没有外部引用Entry内的key,该key(ThreadLocal对象)会被虚拟机回收。但是value并不会。清除value要等到resize操作中。扫描entry数组,发现key是null的,就直接把value也制成null,保证GC。
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
ThreadLocal实现中没有使用锁。但是使用了AtomicInteger。这是为了服务于ThreadLocal该类的静态方法,nextHashCode。该类的初始nextHashCode被置为0,每一次调用都返回该值并加上0x61c88647。这个值用来标志该ThreadLocal对象的唯一序号。
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
为什么ThreadLocal要有唯一序号呢?因为ThreadLocal是要作为上面那个map的key。我们知道map的底层是数组实现的,将ThreadLocal的序列号与上容量,就可以快速定位数组中的entry对象了
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);
}