ThreadLocal 并不是一个线程,它是线程内部的数据存储类,它的数据不是共享的,只有在当前线程才能获取到,在当前线程存储的数据,意思就是说,A,B两个线程,在A线程把数据存储进ThreadLocal,只有在A线程才能取出,B线程取不到。
举个例子
public static void main(String[] args) throws Exception {
sThreadLocal = new ThreadLocal<>();
sThreadLocal.set("主线程");
new Thread("A"){
@Override
public void run() {
super.run();
sThreadLocal.set("A线程");
System.out.print("A --- "+sThreadLocal.get()+"\n");
}
}.start();
new Thread("B"){
@Override
public void run() {
super.run();
System.out.print("B --- "+sThreadLocal.get()+"\n");
}
}.start();
System.out.print("main --- "+sThreadLocal.get()+"\n");
}
打印的日志如下:
A --- A线程
main --- 主线程
B --- null
是不是很奇妙,虽然大家公用一个ThreadLocal对象,但是取出的数据却完全不一样,这是因为通过get方法,ThreadLocal会先取出一个ThreadLocalMap.Entry对象,再从Entry中获取Value。
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();
}
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
但是怎么确保当前线程的Entry对象和另外线程的Entry对象不同呢?
先看ThreadLoacl的内部类ThreadLocalMap
成员变量
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
//
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/*
* 初始容量
*必须是2的幂。
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 存储Entry的数组
* table的大小是2的幂次方
*/
private Entry[] table;
/**
* 表中的项数。
*/
private int size = 0;
/**
* 加载因子。也就是当容量达到多少需要扩充
*/
private int threshold; // Default to 0
/**
*大于长度的2/3就会扩充
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
}
Entry对象是ThreadLocalMap的内部类,同时继承了WeakReference
构造方法
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);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
构造方法有两个。
第一个构造方法:创建了一个默认大小容量的数组,然后根据ThreaLocal的哈希值来做为table的角标存储第一个数据,同时扩容容量。
第二个构造方法:传入
存储Entry对象
//用来遍历 0,1,2,3--->遍历顺序是 1,2,3,0
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//通过hashCode来找到当前ThreadLocal所在的位置
int i = key.threadLocalHashCode & (len-1);
//从当前位置出发,不停的向后移动一位,再数组不填满的情况下,会从当前位置一直遍历到最后一个元素
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果ThreadLocalHashCode值相同,说明是同一个线程里面的参数 就覆盖掉先前的值
if (k == key) {
e.value = value;
return;
}
//如果为null,因为entry是一个弱引用,说明被回收掉了
if (k == null) {
//替换掉陈腐的Entry 也就是把过期的给替换掉
replaceStaleEntry(key, value, i);
return;
}
}
//获取到return后的i 新建一个Entry存进去
tab[i] = new Entry(key, value);
int sz = ++size;
//判断需不需要扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
这里面的逻辑很清晰,就是把ThreadLocal和值以键值对的形式存给Entry,再把Entry对象存入到数组中。最后再判断一下需不需要扩容。
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
//从slotToExpunge开始反序循环 找到slotToExpunge之前最前面的失效元素的位置,如果没找到,
// 就是当前的位置为失效条目
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
//从当前失效元素的位置开始,正序遍历
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
//把失效元素与当前交换一下位置,保证哈希表的顺序。
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 如果存在之前的陈旧元素,就开始删除
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// If we didn't find stale entry on backward scan, the
// first stale entry seen while scanning for key is the
// first still present in the run.
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// If key not found, put new entry in stale slot
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
//删除所有的过期元素
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
获取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);
}
这个方法比较简单,通过传入的ThreadLocal的hashCode直接计算i的值,从而获取对应位置的Entry。
到这里,大概对ThreadLocalMap这个对象有点了解了,回过头看ThreadLocal。
构造方法
public 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);
}
进来,获取当前线程对象,通过getMap()获取ThreadLocalMap对象。
这个方法其实返回的是Thread里面的一个变量,通过变量进行存储,确保Thread对应唯一的ThreadLocalMap。
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
判断map是否为null,如果不为空则直接调用ThreadLocalMap的set方法,上面我们也分析过了,会以类似键值对的形式存入进Entry[]数组中。
如果map为null,则调用createMap方法,该方法是new 了一个ThreadLocalMap 并把值赋值给了当前Thread对象中的threadLocals变量。
这样就成功的把ThreadLocalMap与当前Thread进行了绑定,所以才可以在不同的Thread中,获取到不同的数据。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
获取方法 get
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();
}
同样是先获取到ThradLocalMap对象,再调用其getEntry方法,把当前ThreadLocal对象做为key传进去,获取到对应的entry对象,再从entry获取到value。
如果没有成功找到对应的Entry,则会调用setInitialValue方法,返回默认值。判断ThreadLocalMap是否为空,不为空,把当前的threadLocal和默认值Value 存进Map的数组中,如果为null,则创建map。
private T setInitialValue() {
T value = initialValue();
//获取value值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
//存值进去
map.set(this, value);
else
//创建map
createMap(t, value);
return value;
}
//初始值
protected T initialValue() {
return null;
}
总结
大概捋一下几个类的关系,这里面又Thread、ThreadLocal、ThreadLocalMap、Entry四个对象。
其中 Thread 持有ThreadLocalMap对象做为成员变量。
ThreadLocalMap是ThreadLocal的内部类。
Entry是ThreadLocalMap的内部类,同时继承WeakReference
Entry有ThreadLocal和Object value两个成员变量,以类似键值对的形式保存在ThreadLocalMap中的Entry[]数组中,以当前的ThreadLocal做为key,Object做为value。
所以当,在一个线程A中调用set方法 存入数据,会先获取当前的Thread对象,检查该对象的ThreadLocalMap,接着把值和ThreadLocal保存到ThreadLocalMap的数组Entry[]中(通过hashCode确定在数组中的位置),取值的时候,通过ThreadLocal,来寻找对应数组中的Entry对象,从而获取到值。
而如果是在线程B中取值,当前的Thread对象中的ThreadLocalMap还为null,所以查不到在A线程存入的值。