首先,ThreadLocal是用来进行线程间的数据隔离的。我们知道一般对于共享数据的安全防护是用锁来实现的,这里我们看看ThreadLocal是怎么实现线程隔离从而保证数据安全的。
其起作用的机制是为每个线程提供一个独立的变量副本。
我们看是怎么做到的
1、在Thread类当中,有如下代码
ThreadLocal.ThreadLocalMap threadLocals = null;
也就是说每个线程Thread都会持有一个ThreadLocalMap引用。
2、ThreadLocalMap
ThreadLocalMap是ThreadLocal当中的一个静态内部类,其实就是记录以ThreadLocal为键,要进行隔离的变量对象为值的一个Map映射,从其名称上也可以看出来。
......
// 由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变量
private Entry[] table;
3、然后就是ThreadLocal中的几个重要方法get、set等
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();
}
其中getMap操作就拿到了1中说的线程的ThreadLocalMap对象,这里的线程是当前线程。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
如果是线程的ThreadLocalMap对象还未创建,则进行创建,可以看出创建时传入的是this(ThreadLocal)。
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;
}
// ThreadLocalMap的创建
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
再看看set方法,set就是把ThreadLocal和与之对应的值组成的Entry放入到2中 Entry[] table 中具体位置的过程
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
4、同一线程Thread中使用到多个ThreadLocal的处理(多个不同类型的变量)
前边我们看到,每个线程Thread都持有一个ThreadLocalMap变量,然后通过ThreadLocalMap的Entry[] table来存储每一个ThreadLocal和值对象组成的Entry。多个ThreadLocal变量怎么处理,关键是看放到了哪个具体的位置,看以下关键代码
Entry[] tab = table;
int len = tab.length;
// 计算出该ThreadLocal变量应该存放在Entry[] table数组当中的哪个位置
int i = key.threadLocalHashCode & (len-1);
在当前线程中,每次new ThreadLocal的时候,threadLocalHashCode的值均会以HASH_INCREMENT为步长递增更新。在进行get和set操作时,均由 key.threadLocalHashCode & (len-1) 计算其在数组中的索引。
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
其它细节这里不做探究。
5、总结
综上我们发现ThreadLocal实现线程隔离其实不难,就是每个线程持有变量副本,各线程操作各自的Entry[] table数组,实现互不干涉。这种设计其实挺巧妙,和锁比较的话,对应是空间换时间的一种方式,所以其并发性能显然更好。