一、首先要了解ThreadLocal是什么东西,这个有什么用?
ThreadLocal是jdk 1.2开始提供的一个类,作用是为了解决在多线程中并发访问的一种方式,为每一个线程提供一个独立的副本,这个副本只对当前线程可见,其他线程不能访问,从而隔离了多个线程对数据的访问冲突。在Android应用中我们会经常看到他的影子,如Handler中,EventBus中都有用到它。
二、再来看看它的使用:
class MainClass {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new TestRunnable());
Thread thread2 = new Thread(new TestRunnable());
thread1.start();
thread2.start();
}
private static ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static class TestRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
local.set(local.get() + 1);
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "===" + local.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
输出结果如下:
上面的代码是开启了两个线程,并且在线程了对同一个ThreadLocal实例变量local进行了get和set的操作。从上面的结果可以看到两个线程内的local的get和set互不影响。为什么会出现这样的结果呢?
下图是它的原理图:
好,我们根据上图去查看它的源码分析。
三、ThreadLocal的源码分析。
1、首先看它的构造函数:
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
2、然而它的构造函数什么也没有,我们上面的例子用到了它的get()和set()方法,那我们去看一下它的get()方法的实现。
public T get() {
Thread t = Thread.currentThread(); //获取当前的线程
ThreadLocalMap map = getMap(t); //通过线程拿到当前线程的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 通过this去拿到ThreadLocalMap的值,this为ThreadLocal对象
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); //如果当前线程的ThreadLocalMap 为空,则返回默认值
}
2.1 这里先不管这这个ThreadLocalMap内部是什么样的,我们只知道它是以ThreadLocal为key存取的Entry对象的map,反正它肯定和我们保存的数据有关就是了。
我们来看看getMap()这方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //噢,原来它是线程实例的一个成员变量,也就是每个线程都持有一个这样的变量,来到这里,我们是否有了一点思路。既然这是每个线程的变量,那这个map存取到的数据肯定是为该线程所有的啦。
}
2.2 然后继续看下去 setInitialValue()方法:
private T setInitialValue() {
T value = initialValue(); //是不是很熟悉,上面例子创建ThreadLocal时重写了该方法,就是为ThreadLoca的get()方法提供默认值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
很简单,就是map为空,则创建ThreadLocalMap实例并赋值给当前线程的threadLocals 变量
3、好了,我们再来看看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);
}
同样,和get()很类似,都是先拿到当前线程的map,如果不为空就赋值,为空就创建再赋值。
4、来到这里,其实ThreadLocal的原理框架已经很明了啦。那我们再来看看这个ThreadLocalMap内有什么乾坤。ThreadLocalMap是ThreadLocal的静态内部类。
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;
}
}
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
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 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);
}
//set方法
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();
}
}
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal key) {
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.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
//ThradLocal的扩容
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
.........................
}
上面贴出了ThreadLocalMap的主要代码,包含了 get(),set(),remove()等普通数据结构该有的常用方法,也包含了类似HashMap的扩容方法(默认长度16,达到长度75%,扩容两倍,有兴趣可以查看HashMap的源码)。
我们可以看到了这个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将ThreadLocal实例作为key,副本变量value存储起来。注意Entry中对于ThreadLocal实例的引用是一个弱引用,还引用定义在Reference类(WeakReference的父类中),下面是 super(k) 最终调用的代码:
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
这里会出现内存泄漏的情况。
ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。但是当thread结束后,thread就不存在与栈中,强引用会断开,这时thread,map,value都会得到释放被GC回收。所以内存泄漏的情况只是出现在threadLocal置为null而线程还没结束的区间内,而我们正确的写法就是要在使用完之后及时调用ThreadLocal的remove方法去移除对于的Entry。