前言:
我们都知道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);
}
1、Thread t = Thread.currentThread();
首先获取当前正在执行的线程t,
2、ThreadLocalMap map = getMap(t);
//ThreadLocal类中的方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//Thread类中方法:
ThreadLocal.ThreadLocalMap threadLocals = null;
原来当前线程Thread中有一个ThreadLocalMap对象,我们来看看这个ThreadLocalMap对象:
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> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
很明显ThreadLocalMap是一个存在与ThreadLocal的静态内部类,同时包含一个Entry成员对象(Entry也是一个静态内部类),从Entry的构造函数可以看出来,key=ThreadLocal,value是一个object对象(现在可以假想就是要设置的值),接下来我们回到set方法上;
3、if (map != null) map.set(this, value);
接着主要看看ThreadLocalMap的set方法,其实是调用的静态内部类Entry的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;
//获取当前key在Entry数组中的索引位置i
int i = key.threadLocalHashCode & (len-1);
//获取索引位置i,也就是在tab数组的起始位置tab[i],往后循环查找
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//获取当前Entry对象的key值(是ThreadLocal对象)
ThreadLocal> k = e.get();
//比较key对象是否相等,就是比较是否存在相同的ThreadLocal
//如果已经存在,替换value值,并返回
if (k == key) {
e.value = value;
return;
}
//如果k为null,k将被回收,则这个e已经是没意义的的元素。调用replaceStaleEntry
//方法删除table中所有无意义的元素.并插入新元素,返回
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//如果索引i处没有元素,则插入一个新的Entry对象
tab[i] = new Entry(key, value);
int sz = ++size;
//判断是否需要扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
这个方法大致的意思是将ThreadLocal作为Entry对象的key,要设置的值作为Entry对象的value;
4、else
createMap(t, value);
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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);
}
如果ThreadLocalMap对象不存在,那么就new一个ThreadLocalMap,将ThreadLocal作为Entry对象的key,要设置的值作为Entry对象的value,然后将ThreadLocalMap对象设为当前对象的threadLocals成员变量;
接下来看看get方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//如果Map对象不等于null
if (map != null) {
//通过key=当前的ThreadLocal获取Entry对象
ThreadLocalMap.Entry e = map.getEntry(this);
//如果Entry对象不为null,直接获取存在的value对象
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//否则调用setInitialValue方法
return setInitialValue();
}
private T setInitialValue() {
//这是一个protected方法,意思就是需要被子类覆盖重写的方法
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
汇总:ThreadLocal能做到线程之间数据隔离的奥秘在于每个线程对象都有一个ThreadLocal.ThreadLocalMap对象,我们可以从宏观上简单的理解为这就是一个Map(其实ThreadLocalMap里面有一个Entry数组,每一个Entry对象存储一个TheadLocal和一个value),Map的key都是ThreadLocal对象,value就是每个线程设置的值,不同的线程是不一样的,这样就做到了线程之间的数据隔离的目的;
java简单的示例:
package eureka.server;
public class Test {
ThreadLocal longLocal = new ThreadLocal();
ThreadLocal stringLocal = new ThreadLocal();
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
}
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
public static void main(String[] args) throws InterruptedException {
final Test test = new Test();
test.set();
System.out.println("父线程 main :");
System.out.println(test.getLong());
System.out.println(test.getString());
Thread thread1 = new Thread() {
public void run() {
test.set();
System.out.println("\n子线程 Thread-0 :");
System.out.println(test.getLong());
System.out.println(test.getString());
};
};
thread1.start();
Thread.sleep(1000);
System.out.println("父线程 main :");
System.out.println(test.getLong());
System.out.println(test.getString());
}
}
运行结果:
Thread可能会产生OOM(内存泄露),ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
Thread->ThreadLocal.ThreadLocalMap.Entry(key =ThreadLocal, value = object),key是一个弱引用,会被垃圾回收,但是value由于Thread的存在会一直存在;