对ThreadLocal的理解
- ThreadLocal能够提供线程的局部变量,让每个线程都可以通过操作set/get来对这个局部变量进行操作
- 不会和其他线程的局部变量进行冲突,实现了线程的数据隔离
ThreadLocal的基本用法
使用set(T value)方法将值存入ThreadLocalMap,如果map不存在,则进入set()内部的else分支创建一个ThreadLocalMap并将值value存入map,在创建的过程中会将这个map保存到当前线程对象的成员属性上
然后调用get()方法获取值,get()内部会先从线程对象取出ThreadLocalMap,然后再取出其中的值,并返回
使用完后我们需要调用remove()方法清除ThreadLocalMap中存的内容,防止内存泄漏
注:Thread对象内部持有一个ThreadLocal.ThreadLocalMap threadLocals 对象,ThreadLocalMap看起来像一个map,可平常使用的过程中就相当于一个变量而已,只能存一个value,如果同一个线程调用两次set,那么只保存后面的set值!
简化的源码结构
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode(); // 下一个hash值
private static final int HASH_INCREMENT = 0x61c88647; // hash增量
private static AtomicInteger nextHashCode = new AtomicInteger();// hash值的原子变量
public ThreadLocal() {} // 构造方法
/**
* get方法、重点阅读
*/
public T get() {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 获取当前线程绑定的ThreadLocalMap
if (map != null) { // 如果map不为null
ThreadLocalMap.Entry e = map.getEntry(this); // 获取当前线程绑定的数据的封装对象Entry(内部类)
if (e != null) { // 如果当前数据不为null
T result = (T) e.value; // 获取当前数据绑定的数据,进行了强转为泛型
return result; // 返回数据
}
}
return setInitialValue(); // map为null返回null
}
/**
* set方法、重点阅读
*/
public void set(T value) {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 获取当前线程绑定的 ThreadLocalMap
if (map != null) { // map不为null
map.set(this, value); // 给当前线程绑定的map中添加数据,key是当前线程、值是传入的value
} else {
createMap(t, value); // 当前线程没有map,创建一个map将传入的value存入map中并绑定到当前线程对象
}
}
/**
* remove方法、
* 移除当前线程绑定的数据,
* 每次使用完后就需要进行remove防止内存泄漏!!!
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread()); // 获取当前线程绑定的map
if (m != null) { // 存在map
m.remove(this); // 清除当前线程绑定的数据
}
}
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {} // 返回一个新的ThreadLocal变量
/**
* 外部使用不到的方法
*/
protected T initialValue() { return null; } // 初始值null
private static int nextHashCode() { } // hash值加上0x61c88647并且将加完后的值返回
private T setInitialValue() {} // 返回null并设置初始值null和set方法一样的逻辑,只是value为null
ThreadLocalMap getMap(Thread t) { } // 得到线程t绑定的ThreadLocalMap
void createMap(Thread t, T firstValue) {} // 创建ThreadLocalMap并添加key=t,value=firstValue到map中并绑定到当前线程
boolean isPresent() {} // 是否存有数据,有则返回true、没有则返回false
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {} // 创建ThreadLocalMap,只在构造方法中使用
T childValue(T parentValue) {} //
// ThreadLocal类的扩展,获得规定的初始值Supplier(使用供给型的接口),实际上不需要了解,这个类我们外面使用不到
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) { this.supplier = Objects.requireNonNull(supplier); }
@Override
protected T initialValue() { return supplier.get(); }
}
/**
*
*/
static class ThreadLocalMap {
private static final int INITIAL_CAPACITY = 16; // 初始容量
private Entry[] table; // 存储数据的结构
private int size = 0; // 当前存了多少个数据
private int threshold; // 扩容条件默认 10
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // 真正存到ThreadLocal中的数据
Entry(ThreadLocal<?> k, Object v) {} // Entry构造方法
}
// 构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY]; // 创建长度为16的Entry数组
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 通过hash值&运算得到索引值
table[i] = new Entry(firstKey, firstValue); // 将数据存入索引值所在的数组中
size = 1; // 添加了第一个值,size变为1
setThreshold(INITIAL_CAPACITY); // 设置扩容值 16*2/3 = 10
}
/**
* 和上面的构造函数差不多,只不过传入的是一个map
* 因此需要讲map中数据存入到线程所绑定的map中
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {}
/**
* 定义的方法用于在构造方法中使用,目的就是给成员变量赋值
*/
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {}
private void resize() {} // 扩容
private void rehash() {} // 再hash
private Entry getEntry(ThreadLocal<?> key) {} // 根据key获取Entry
private void setThreshold(int len) { } // 设置扩容条件值:threshold = len * 2 / 3;
private static int nextIndex(int i, int len) {} // 上一个索引:return ((i + 1 < len) ? i + 1 : 0);
private static int prevIndex(int i, int len) {} // 下一个索引:return ((i - 1 >= 0) ? i - 1 : len - 1);
private void set(ThreadLocal<?> key, Object value) {} // 添加数据
private void remove(ThreadLocal<?> key) {} // 移除key所对应的Entry数据
private int expungeStaleEntry(int staleSlot) {} // 返回下一个null值得插槽索引
private boolean cleanSomeSlots(int i, int n) {} // 如果索引i所对应的数据被移除返回true
private void expungeStaleEntries() {} // 删除所有过时元素
}
}
使用场景
ThreadLocal的使用场景
- springmvc的拦截器传参到控制层
- 一个参数要传入多个方法
- spring使用ThreadLocal来实现事务的控制,保证一次事务的所有操作需要在同一个数据库链接上
- 。。。
关于ThreadLocal的内存泄露
面试题
1.为什么要把ThreadLocal作为key,而不是Thread作为Key?这样不是更清晰吗
答:你提出的做法实际上就是所有的线程都访问ThreadLocal的Map,而key是当前线程,但是这有一个问题,一个线程可以有多个私有变量,那如果key是当前线程的话,意味着还要坐点手脚来唯一表示set进去的value,并且还要一个问题,当并发足够大时,意味着所有的线程都去操作同一个map,map体积会膨胀,导致访问性能下降,而且这个map维护所有线程私有变量,以为这你不知道什么时候可以销毁