ThreadLocal不是JUC并发包下工具,它在java.lang包下面。多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
如果创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量。
ThreadLocal的线程局部值是由它的静态内部类ThreadLocalMap维护的。
知道他的逻辑组成之后,直接看源码.从内往外看,首先看ThreadLocalMap的组成。
static class ThreadLocalMap {
// Entry是ThreadLocalMap哈希数组中的元素
// 键是ThreadLocal对象,值是ThreadLocal set的值,同时也是个弱引用
// entry.get()=null 的时候意味着Entry不再引用该键
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
// WeakReference的构造函数
super(k);
value = v;
}
}
/**
* 初识容量必须为2的次幂
*/
private static final int INITIAL_CAPACITY = 16;
/**
* ThreadLocalMap的哈希表,表的长度始终是2的次幂
*/
private Entry[] table;
/**
* 哈希表中元素的个数
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
}
我们走一遍使用主流程,构造函数没什么好看的了。
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
接着set方法
// 将此线程局部变量的当前线程副本设置为指定值
public void set(T value) {
Thread t = Thread.currentThread();
// 获取当前线程绑定的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 若当前线程有绑定ThreadLocalMap,那么用新value覆盖旧value
if (map != null)
map.set(this, value);
else
// 否则创建ThreadLocalMap
createMap(t, value);
}
// 获取当前线程绑定的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
// Thread类里面有个threadLocals属性就是每个线程绑定的ThreadLocal.ThreadLocalMap
return t.threadLocals;
}
让我们进到ThreadLocalMap的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.
// 拿到hash表并且计算出当前key的hash值和HashMap很像
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 遍历hash表找到对应的key,用新值替换旧值
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal> k = e.get();
// 找到key了用新值替换旧值
if (k == key) {
e.value = value;
return;
}
//若当前key不再被引用了,就清除和占用该过期槽
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 没有找到对应的key,那么在计算的索引位置新建hash表中的元素Entry
tab[i] = new Entry(key, value);
int sz = ++size;
//当清理成功同时到达阈值,需要扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
set方法的主流程完事了,我们看下get方法
public T get() {
// 获取当前线程绑定的ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 用当前ThreadLocal,也就是key获取值Entry,然后返回Entry中的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 若当前线程没有绑定ThreadLocalMap,那么初始化一个,并返回值
return setInitialValue();
}
// 为当前线程初始化一个ThreadLocalMap,并返回值
private T setInitialValue() {
T value = initialValue();
// 再次检验当前线程是否绑定ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
// 为指定线程创建一个ThreadLocalMap,指定值,并且绑定到指定线程
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
最后需要看的是remove方法,删除当前线程绑定的ThreadLocalMap中的值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
// 根据指定的key(ThreadLocal),删除对应的值
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)]) {
// 遍历hash表找到对应的key,清除Entry的值
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
Q:ThreadLocal是怎么实现线程局部变量的?
A:每个线程也就是Thread类都有自己的threadLocals属性,它是ThreadLocal的静态内部类ThreadLocalMap,在ThreadLocal,set值和get值的时候都会去取当前线程的threadLocals属性,用threadLocals来完成set和get操作。每个Thread的threadLocals都是独立的,他们不共享。
Thread的threadLocals属性定义如下:
ThreadLocal.ThreadLocalMap threadLocals = null;