先上一段代码
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("hello");
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("world");
}
}).start();
Thread.sleep(1000);
String str = threadLocal.get();
System.out.println(str);
}
}
打印结果如下
结合上面代码可以看到在别的线程中给ThreadLocal设置值并不不会影响到当前线程,下面我们就从源码角度进行分析
ThreadLocal#set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
上面代码中可以看到ThreadLocal并不是自己把数据进行存储而是取出了线程中一个ThreadLocalMap类型的变量,threadLocals在Thread中声明如下
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
接下来看到ThreadLocal的set方法中存储数据存储的地方
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
第一次取当前线程的threadlocals时是为nul创建代码如下
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
在创建的同时会将值进行存储
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始容量为16
table = new Entry[INITIAL_CAPACITY];
//相当于是hashCode对初始容量取余
//最终i值为0-15的值,就是存储在table的index
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//存储到计算出的下标位置
table[i] = new Entry(firstKey, firstValue);
//记录已经存储数据的个数
size = 1;
//计算加载因子
setThreshold(INITIAL_CAPACITY);
}
上述代码就是创建于存储的过程是将ThreadLocal自身为key对数据进行存储,接来看看下Entry是个什么数据结构
/**
* 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;
}
}
Entry源码上可以看到继承WeakReference那么entry也将会是个弱引用不会影响对象的gc
弱引用对象,不会阻止它们的引用对象被最终化、最终化,然后被回收。弱引用最常用于实现规范化映射。假设垃圾收集器在某个时间点确定一个对象是弱可达的。那时,它将原子地清除对该对象的所有弱引用以及对通过强引用和软引用链可以访问该对象的任何其他弱可达对象的所有弱引用。同时它将声明所有以前的弱可达对象都是可终结的。在同一时间或稍后的某个时间,它会将那些在引用队列中注册的新清除的弱引用排入队列。
接下来看到ThreadLocalMap
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)]) {
//替换值
if (e.refersTo(key)) {
e.value = value;
return;
}
if (e.refersTo(null)) {
replaceStaleEntry(key, value, i);
return;
}
}
//上面for中没有执行或者没有return则代表是新插入值
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
//删除无效的值
rehash();
}
后续删除无效数据以及扩容相关感兴趣的小伙伴可以自行查看,这里不多赘述
ThreadLocal#get
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//通过ThreadLocal对象的hashCode计算下标取得entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//取出entry中的value并返回
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
源码看到这里ThreadLocal的重点基本看完了总结如下
ThreadLocal自身并不存储数据相当于一个中介,我们把数据set到ThreadLocal后,ThreadLocal是取到了当前Thread对象的threadlocals变量中这就是为什么使用ThreadLocal存储数据线程之间互不影响的原因