1.ThreadLocal概念
ThreadLocal,可以叫做线程本地变量或线程本地存储,顾名思义就是ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。其实就是通过空间换时间的方式来取得对每个线程各自变量的共享。
变量值的共享可以使用 public static 变量的形式,所有的线程都使用同一个被 public static 修饰的变量。ThreadLocal主要解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻成全 局存放数据的盒子,盒子中可以存储每个线程的私有变量。
2.具体源码分析
-
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(); }
1.获取当前线程t;2.通过getMap方法获取一个ThreadLocalMap类型的map;3.获取这个键值对,注意这里获取键值对传进去的是 this,而不是当前线程t。
如果获取成功,则返回value值。如果map为空,则调用setInitialValue方法返回value。
-
getMap方法:
ThreadLocalMap getMap(Thread t) { return t.threadLocals; } //Thread类 ThreadLocal.ThreadLocalMap threadLocals = null;
根据传入的当前线程t返回它的局部变量threadLocals,而这个threadLocals实际上是ThreadLocalMap类型的,而ThreadLocalMap又是ThreadLocal的一个内部类。
-
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<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
ThreadLocalMap中的Entry继承了WeakReference,并且使用ThreadLocal类型的值作为key。
-
接下来看get方法中返回的setInitialValue方法:
/** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ 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); return value; }
当map不为空时,将键值对存进去,当map为空时,重新创建一个map
-
protected T initialValue() { return null; }
initialValue方法默认返回空,所以要先调用set方法才可以调用get方法,如果想要先调用get方法的话,需要重写initialValue方法。
-
createMap方法:
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
-
- get方法:
到这里基本就可以看出ThreadLocal是如何为每个线程创建变量的副本的:
首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,key为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
- 例子:
public class Test {
ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
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(test.getLong());
System.out.println(test.getString());
Thread thread1 = new Thread(){
public void run() {
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
};
};
thread1.start();
thread1.join();
System.out.println(test.getLong());
System.out.println(test.getString());
}
}
这段代码的输出结果为:
从运行结果看出:因为实例化了两个ThreadLocal变量,所以他们对各自的局部变量保存的副本值是不一样的,因此在两个线程中的执行并不会互相影响各自的变量值。
总结一下:
1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;
3)在进行get之前,必须先set,否则会报空指针异常;
如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法,设置默认值。
因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。
3.值继承
使用InheritableThreadLocal类可以实现值的继承,让子线程从父线程中取得值。不过要注意,如果子线程在取得值的同时,父线程将InheritableThreadLocal中的值进行修改,那么子线程取到的还是原来的值。