ThreadLocal源码阅读记录,如有不妥,欢迎指出,共同学习,谢谢!
一、简单使用
ThreadLocal可以理解为线程本地变量,使用它,可以将变量与线程之间产生关联,使得变量的隔离级别在同一个线程中.我们通过非常简单的代码,来简单使用下ThreadLocal:
private static ThreadLocal<Integer> mThreadLocal;
public static void main(String[] args) {
mThreadLocal = new ThreadLocal<>();
mThreadLocal.set(50);
System.out.println("当前线程:" + Thread.currentThread().getName() +
",ThreadLocal取出值:" + mThreadLocal.get());
new Thread(() ->
System.out.println("当前线程:" + Thread.currentThread().getName() +
",ThreadLocal取出值:" + mThreadLocal.get())
).start();
}
上面的代码非常简单,首先我们在主线程往mThreadLocal中set了一个Integer类型的变量,值为50.紧接着我们马上取出值:mThreadLocal.get()
,得到了刚才存进去的值:50.然后我们在子线程中再次尝试取出值,但却发现取出来的结果为:null.因此上面代码的运行结果为:
当前线程:main,ThreadLocal取出值:50
当前线程:Thread-0,ThreadLocal取出值:null
这也证实了,使用ThreadLocal存储的变量,是属于当前线程的,在其它线程是访问不了的.
二、源码阅读
首先我们还是从最简单的使用说起:ThreadLocal<Integer> threadLocal = new ThreadLocal<>()
,显然这里可以看出ThreadLocal是一个泛型类,其泛型表示希望存储数据的数据类型,ThreadLocal的构造器如下:
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
似乎没什么好说的... 接着我们再来看 threadLocal.set(50)
,这里又是怎么回事呢?跟进到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对象引用.这个ThreadLocalMap是啥呢?它是ThreadLocal的一个静态内部类,这里先大概知道这样就行,还是先看下getMap()
方法是怎么回事:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
问号猫
看到这里,原来ThreadLocalMap的引用存储在Thread类的成员变量中:
ThreadLocal.ThreadLocalMap threadLocals = null;
好吧,回到我们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);
}
}
继续往下看,因为我们目前是第一次set值,所以getMap返回的是null,也就是这里的map为null,所以会走createMap(Thread t, T firstValue)
方法,我们又跟进到此方法中看一看:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
实际上就是创建了一个ThreadLocalMap对象,并把对象引用赋值给当前Thread对象中的threadLocals字段.继续看下ThreadLocalMap的构造器:
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);
}
首先看下这个构造器的参数列表,第一个参数为ThreadLocal对象,传给它的是this(当前类[TreadLocal]对象),第二个参数为Object类型变量,传给它的是调用set方法时传进来的参数,也就是要保存的数据.明白构造器两个参数都接收的是什么东西后,再接着看代码.
第一行:table = new Entry[INITIAL_CAPACITY];
Entry是什么呢?它是ThreadLocalMap的静态内部类:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
可以看到这个类继承了WeakReference
,代表Entry对象弱引用了ThreadLocal,并将用户通过set方法传进来的内容赋值给成员变量value.再来看下table的定义:private Entry[] table;
,因此第一行table = new Entry[INITIAL_CAPACITY];
就是声明并初始化了一个Entry数组,数组的长度为INITIAL_CAPACITY
也就是16(private static final int INITIAL_CAPACITY = 16;
)
继续回到ThreadLocalMap的构造器往下看:
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);
}
第二行以后是将当前ThreadLocal对象的HashCode 和 table数组长度-1 做了与运算,得到了一个int类型的值,将这个值作为数组table的下标,并将Entry对象引用作为值存储.
至此,就完成了对set数据的存储.回顾一下上面的流程,实际上就是:每一个Thread中都存储有ThreadLocalMap的引用,每次调用set方法时,都会获取当前线程的ThreadLocalMap进行存储数据,而ThreadLocalMap内部有一个Entry数组table,以当前ThreadLocal的HashCode & table.length-1为下标,Entry中存储的是当前ThreadLocal对象的弱引用和set保存的数据.如果已经通过ThreadLocal对象往当前线程中设置了数据,再次往该线程中设置数据时:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
这个map就不再是null了,因此会走ThreadLocalMap的set方法:
private void set(ThreadLocal<?> key, Object value) {
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();
}
首先获取当前Thread中的ThreadLocalMap中的table,赋给tab.用同样的方法得到此ThreadLocal对象的下标,如果tab[i]得到的Entry不为null,表示曾经使用过当前ThreadLocal对象往当前线程中设置过数据,则会进入for循环,若tab[i]为null,表示当前ThreadLocal对象从未在该线程中设置过数据,则会执行for循环后面的代码,在table中新增一条记录.再来看tab[i]不为null的情况,for循环首先判断Entry的key也就是ThreadLocal的引用是否和当前的ThreadLocal引用相同,如果相同,则修改Entry中的value为新值,循环结束.如果k==key
不满足,则会去判断k是否为null,若果k为null,则会调用replaceStaleEntry方法,继续寻找有无相同key的Entry,如果有,直接替换Entry的value为新值,若果没有,则会创建新的Entry.
好了,设置值的部分大概就这样了吧,这只是个人理解,如有不正确的地方,欢迎指出.接下来看看获取值得部分.
threadLocal.get()
,同样,我们先看下ThreadLocal的get方法, 想都不用想,肯定是先从当前线程获取到ThreadLocalMap:
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();
}
显然如果我们从来没有使用ThreadLocal给该线程set过数据,那么这里的map肯定是null,则会调用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);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
简要看下这个方法的作用,首先调用initialValue()方法得到value,然后获取当前线程的ThreadLocalMap,如果map为null,则创建map(设置进去的值为value的值),否则直接调用set方法进行设置值,最后都会返回value的值.那最重要的就是看下initialValue()
方法返回的是什么了?
protected T initialValue() {
return null;
}
因此.如果我们从来没有在当前线程使用ThreadLocal设置过数据,直接调用ThreadLocal的get方法,将会返回null.继续回到ThreadLocal的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();
}
如果我们之前使用ThreadLocal在当前线程中设置过数据,那么map就不为null,然后会调用ThreadLocalMap中的getEntry()方法获取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的下标,如果在table中找得到,说明使用此ThreadLocal在当前线程中set或get过,直接返回e,然后再返回e.value就获取到值了.如果在table里找不到下标为i的Entry或是找到了,但是e.getKey()== key
不成立的话,则会调用并返回getEntryAfterMiss()
方法
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;
}
这个方法中,首先就是看Entry是否是null,如果不是null,说明曾在当前线程使用过该ThreadLocal,只是e.getKey()== key
不成立,在while循环中,如果k == null
则说明Entry对ThreadLocal的弱引用丢了,那么就会调用expungeStaleEntry
方法清除脏数据,也就是把table[i]置为null,把Entry中的value置为null避免内存泄漏.如果k != null && k!=key
,就会继续往下查找Entry,并重复执行上面的操作.
网友评论