ThreadLocal:ThreadLocal是线程局部变量,所谓的线程局部变量,就是仅仅只能被本线程访问,不能在线程之间进行共享访问的变量。
ThreadLocal的使用非常广泛,典型的,mybatis的分页插件PageHelper用的就是ThreadLocal。
在我们日常的开发里,最典型的应用就是例如一个请求(单线程)的执行过程要执行很多方法:a->b->c->d->e,假设方法a要用到一个变量,e也要用到这个变量,如果这个变量一直往下传则会显得很臃肿,这个时候,ThreadLocal是个很好的解决方式
直接看一段代码:
public class ThreadLocalTest {
private static ThreadLocal threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
set();
System.out.println(get()); // 打印 abc
}
private static String get() {
return threadLocal.get();
}
private static void set() {
threadLocal.set("abc");
}
}
代码很简单,功能也很清晰。下面我们来看看ThreadLocal是如何做到在一个地方set而在另一个地方get的
private static ThreadLocal threadLocal = new ThreadLocal<>();
跟进去看一下底层源码:
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
发现啥事也没做,也就说,就简单地实例化了一个对象
再来看看它的成员变量赋值情况
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);
}
这段代码比较迷糊,好像就是为了初始化一个常量——threadLocalHashCode,等会我们再回来看看作用
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
从这里看来,ThreadLocal存放的东西的确是跟当前的线程是有关系的,从字面上来理解,字段是存放在了一个map里,而这个map是当前线程的一个成员变量,这个成员变量的类型是ThreadLocalMap。
下面我们来看看这个ThreadLocalMap是什么东西
我们来看看这个方法
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
看看ThreadLocalMap创建的时候发生了什么事
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 void setThreshold(int len) {
threshold = len * 2 / 3;
}
这里我们第1步的比较迷糊的东西出现了——threadLocalHashCode
看看这段代码做了什么,这段代码的逻辑最终作用是——把该ThreadLocal对应的值存在一个成员变量table里,以key/value的形式存储,key是当前的ThreadLocal实例,value就是我们要保存的值
到这里,我们ThreadLocal存值的原理基本解释完毕了,但是还是有遗留问题
(1)Entry
看看Entry的定义(是ThreadLocalMap一个静态内部类)
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
要非常注意,存储ThreadLocal和它的值的是一个虚引用
关于虚引用的意义可以看我这篇文章 强引用、弱引用、软引用和虚引用
所以,当GC发生的时候,定义的ThreadLocal是有可能被回收的
(2)threadLocalHashCode到底用来做什么的
这个参数,我们是用来唯一确定一个ThreadLocal对象的
但是如何保证两个同时实例化的ThreadLocal对象有不同的threadLocalHashCode属性呢?在ThreadLocal类中,还包含了一个static修饰的AtomicInteger成员变量和一个static final修饰的常量(作为两个相邻nextHashCode的差值)。由于nextHashCode是类变量,所以每一次调用ThreadLocal类都可以保证nextHashCode被更新到新的值,并且下一次调用ThreadLocal类这个被更新的值仍然可用,同时AtomicInteger保证了nextHashCode自增的原子性。
确定了唯一的ThreadLocal对象,threadLocalHashCode还作为确定当前线程的ThreadLocalMap的table数组的位置(table数组其实就是Entry数组)
为什么不直接用线程id来作为ThreadLocalMap的key?
这一点很容易理解,因为直接用线程id来作为ThreadLocalMap的key,无法区分放入ThreadLocalMap中的多个value。比如我们放入了两个字符串,你如何知道我要取出来的是哪一个字符串呢?
而使用ThreadLocal作为key就不一样了,由于每一个ThreadLocal对象都可以由threadLocalHashCode属性唯一区分或者说每一个ThreadLocal对象都可以由这个对象的名字唯一区分,所以可以用不同的ThreadLocal作为key,区分不同的value,方便存取。
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();
}
来看看关键代码——e = tab[i = nextIndex(i, len)]
从这里可以看出,如果产生了hash冲突,ThreadLocalMap采用的是再哈希的方式解决冲突的