ThreadLocal是什么
ThreadLocal能使线程中的某个值与保存值的对象关联起来,每个使用改变量的线程都存有一份独立的副本,因此返回的结果总是当前执行线程保存设置的最新值。
上面是摘选自《Java并发编程实战》中文版中对ThreadLocal介绍,其中提到独立的副本,翻译为副本不是一个好的选择,会产生歧义。因为在中文里副本是与主本对应的,会给人一种错觉ThreadLocal存储的是对某一个变量深拷贝之后的变量。下面我们看看英文原版的介绍。
Thread-Local provides get and set accessor methods that maintain a separate copy of the value for each thread that uses it, so a get returns the most recent value passed to set from the currently executing thread.
在英文版中,用a separate copy of the value描述ThreadLocal的存储。这里的copy不应该简单的理解为拷贝复制,而应该理解为大量生产的样品中的一个。换言之,ThreadLocal保存的应该是防止全局共享的对象,大量产生后的一个。例如,数据库的连接Connection对象,由于JDBC的连接对象不是线程安全的,因此可以将JDBC的连接保存在ThreadLocal中,每个线程都拥有属于自己的连接。
上面根据书中的叙述对ThreadLocal做了一个新的认识,但事实仍旧需要通过源代码进行验证,下面讲解ThreadLocal的源代码。
ThreadLocal的set和get方法
set方法
public void set(T value) {
//获得当前的线程
Thread t = Thread.currentThread();
//获取当前线程Thread对象的ThreadLocalMap结构对象
ThreadLocalMap map = getMap(t);
//如果map存在,设置值,key为当前的ThreadLocal对象,值为输入的value
if (map != null)
map.set(this, value);
else//否则需要先创建map
createMap(t, value);
}
get方法
public T get() {
//获得当前的线程
Thread t = Thread.currentThread();
//获取当前线程Thread对象的ThreadLocalMap结构对象
ThreadLocalMap map = getMap(t);
//map不为null时,当前的ThreadLocal对象为key查询到对应的Entry结构,返回value
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//否则,设置初始值null并且返回
return setInitialValue();
}
上面两段代码是ThreadLocal的set和get方法的实现,注意到几点:
- 存储变量的数据结构ThreadLocalMap,是属于线程对象Thread自身的。
- ThreadLocalMap中的key是当前的ThreadLocal对象。
上面两点简单说,就是ThreadLocal对象调用自己的方法,把自己作为key放入到当前调用自己的线程Thread对象的ThreadLocalMap结构中。
- ThreadLocalMap中把value和key封装为Entry结构进行存储
第三点具体看下面Thread的ThreadLocalMap。
Thread的ThreadLocalMap
ThreadLocalMap的set方法
把key-value封装为Entry结构存储到table中。通过计算ThreadLocal对象的哈希值找到对应的桶。
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//ThreadLocal计算哈希值找到对应的桶
int i = key.threadLocalHashCode & (len-1);
//遍历数组tab
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果存在相同的key,替换value
if (k == key) {
e.value = value;
return;
}
//如果查询到key是空的entry,将其清除并且放入新值
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//否则,放入数组的最后的空位上
tab[i] = new Entry(key, value);
int sz = ++size;
//如果需要,扩大数组大小
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
从上面的set方法可以看到key和value被封装为Entry结构存储,并且ThreadLocalMap底层是由Entry数组实现的。
ThreadLocalMap中Entry的弱引用
Entry结构是ThreadLocalMap的一个内部类,继承弱引用WeakReference实现。Entry的构造函数中key生成弱引用存储。
弱引用指向的对象不会熬过第二次垃圾回收,这里使用弱引用是为了作为key的ThreadLocal对象被垃圾回收后,存储在ThreadLocalMap结构中的引用可以被自动回收掉,并且不可以再被使用了。这就是为什么ThreadLocalMap的set方法会查询到key为null的entry结构。只要作为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;
}
}
.....省略余下代码......
网友评论