ThreadLocal类提供一种线程私有的局部变量机制,使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。实际上,每个线程都拥有该ThreadLocal变量的的一个副本,该副本仅对当前线程可见。
熟悉JVM的小伙伴都知道,程序计数器、Java虚拟机栈和本地方法栈这三个内存空间是线程私有的,那么是否意味着ThreadLocal变量(对象)就是存储在其中的某个内存空间中呢?显然是否定的,ThreadLocal实例与普通的java对象一样,都是存储在Java堆中的,只不过JDK源码使用了某种编程技巧,使得ThreadLocal对象为某个线程私有。接下来我们从JDK源码角度分析一下这是如何实现的
要厘清楚ThreadLocal的原理,我们需要从一下几个类着手:
- Thread类:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Thread类中有一个ThreadLocal.ThreadLocalMap类型的属性,这个属性类似于 HashMap结构(JDK7之前),稍后会详细讲解。
- ThreadLocal类:
private final int threadLocalHashCode = nextHashCode();
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();
}
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;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
上面贴出ThreadLocal类主要的几个方法:
1、 threadLocalHashCode属性在ThreadLocalMap集合进行数据存取的过程中起着重要的作用,类似于HashMap根据key的hashCode值来定位存取数据的数组索引一样。正是因为该属性,ThreadLocal对象才能作为key,存储在ThreadLocalMap集合中,即threadLocalHashCode属性让ThreadLocal对象变成"不可变对象"。
2、get方法:获取当前线程的threadLocals属性(ThreadLocalMap对象),若存在的话就以this为键去当前线程的threadLocals属性中查找Entry,并返回其value;若threadLocals属性不存在,则为当前线程创建一个,并设value为null。
3、set方法:set方法也同样简单,仍然是获取当前线程的threadLocals属性,并以this为键存储键值对。
- ThreadLocalMap类:
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;
}
}
......
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);
}
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;
}
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();
}
ThreadLocalMap类是ThreadLocal类的静态内部类,底层维护了一个Entry类型的数组,这个集合结构十分类似于HashMap(JDK7之前),以ThreadLocal对象作为键,对应的值就是当前线程针对该ThreadLocal变量持有的数据。
主要的方法就是getEntry和set方法:
1、getEntry方法就是根据ThreadLocal(key)获取对应的Entry的过程。流程是将ThreadLocal的threadLocalHashCode属性与Entry数组长度作位运算,其结果作为数组的索引,接着获取该索引处的数组元素(Entry对象),判断该元素是否存在且与其包含的ThreadLocal是否key相同,若满足则返回该Entry,若不满足则执行getEntryAfterMiss方法。
getEntryAfterMiss方法中的操作比较简单,就是将索引值依次递增向数组后端推进,直到找到目标Entry元素。这一点与HashMap存在区别:HashMap的底层是数组加向链表,若在数组指定索引处找不到目标元素,则会从该元素所在的单向链表开始,向链表后端查找。
2、set方法就是存储键值对的过程,也是先根据ThreadLocal的threadLocalHashCode属性与数组长度作位运算,获得数组索引,然后从该索引处开始,向数组后端查找,若key相同则覆盖,若不存在Entry则新建...- Entry类:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry类是ThreadLocalMap类的静态内部类,上述已经提及到了,这里需要注意的是Entry类继承了WeakReference<ThreadLocal<?>>类,WeakReference类表示虚引用,继承该类目的就是避免
内存泄漏。
总结:
上述从JDK源码角度讲解了ThreadLocal实现线程私有的原理,总的来说,就是每个线程都维护了个ThreadLocalMap类型的属性threadlocals,这个属性类似于HashMap结构,用来存储键值对,key就是ThreadLocal对象,而value就是当前线程针对该ThreadLocal对象所持有的数据,由于threadlocals是线程私有的,所以ThreadLocal对象对应的数据也为该线程私有。
Ending ...