并发编程-ThreadLocal原理

ThreadLocal是一个本地线程副本变量工具类,ThreadLocal的实例代表了一个线程局部的变量,主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。

1. 是什么

  • 是让线程拥有独占的变量
  • 它通过set、get方法进行设值和取值操作
  • 它可以覆盖initialValue方法设置初始值,在没进行set之前调用get会调用初始化方法,一个线程只会调用一次
  • 每个线程都会有一个指向threadLocal的弱引用,只要线程一直存活或者该threadLocal实例能被访问到,就不会被GC清理掉。当jvm内存溢出时,会清理掉值为Null的弱引用。

2. 使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args){
ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>(){
@Override
protected String initialValue(){
return "default string";
}
};
for(int i = 0; i< 10; i++){
new Thread(() -> {
stringThreadLocal.set(Thread.currentThread().getName());
System.out.println(stringThreadLocal.get());
}).start();
}
}

3. 在一个map里

每个线程都有一个ThreadLocalMap对象,map中存放了(ThreadLocal,t)键值对

1
2
3
4
5
6
7
8
9
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

看看ThreadLocalMap的Entry定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ThreadLocal.ThreadLocalMap
static class ThreadLocalMap {
// 存放条目的数组
private Entry[] table;
//Entry定义
static class Entry extends WeakRefezrence<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

}

ThreadLocalMap的Key是ThreadLocal<?> 引用,value是?类型的值

1
ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>(){

当我们在代码中创建了一个ThreadLocal变量时,此时并没有与某个指定的线程对象关联,那么是什么时候关联起来的呢?

3.1 get源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ThreadLocal.get()
public T get() {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); //获取线程的ThreadLocalMap
if (map != null) { // 若当前操作的ThreadLocal变量第一个的话,那么map肯定返回null
ThreadLocalMap.Entry e = map.getEntry(this); // 从Entry数组中获得本Local变量的Entry
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value; // 获取值
return result;
}
}
return setInitialValue(); // 没有获取到则进行初始化,并返回默认值
}
  • 获取当前线程内部的ThreadLocalMap
  • map存在则获取当前ThreadLocal对应的值
  • 不存在则调用setInitialValue进行初始化

3.2 setInitialValue()源码

1
2
3
4
5
6
7
8
9
10
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); // 为Thread创建map,并将当前的k-v放进去
return value;
}
  • 调用重载的initialValue方法获取初始值
  • 获取当前线程的ThreadLocalMap
  • map存在则将初始值put进去
  • map不存在则使用初始值为当前线程创建ThreadLocalMap

3.3 set(T value)源码

1
2
3
4
5
6
7
8
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
  • map存在则把当前ThreadLocal和value添加到map中
  • map不存在则创建一个ThreadLocalMap,保存到当前线程内部

总结:
每个线程都有一个ThreadLocalMap类型的私有变量,当为线程添加ThreadLocal对象时,就是保存到了这个map中,所以线程之间不会相互干扰。

4. 我还有一个大坑

ThreadLocal使用不当,会引发内存泄露的问题

ThreadLocal对象存在thread对象中,只要线程没有死亡,该对象就不会被回收

remove()源码

1
2
3
4
5
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

  • 获取当前线程内部的ThreadLocalMap,存在则从map中删除这个ThreadLocal对象。