对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); // 获取当前线程绑定的ThreadLocalMapif (map != null) { // 如果map不为nullThreadLocalMap.Entry e = map.getEntry(this); // 获取当前线程绑定的数据的封装对象Entry(内部类)if (e != null) { // 如果当前数据不为nullT 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); // 获取当前线程绑定的 ThreadLocalMapif (map != null) { // map不为nullmap.set(this, value); // 给当前线程绑定的map中添加数据,key是当前线程、值是传入的value} else {createMap(t, value); // 当前线程没有map,创建一个map将传入的value存入map中并绑定到当前线程对象}}/*** remove方法、* 移除当前线程绑定的数据,* 每次使用完后就需要进行remove防止内存泄漏!!!*/public void remove() {ThreadLocalMap m = getMap(Thread.currentThread()); // 获取当前线程绑定的mapif (m != null) { // 存在mapm.remove(this); // 清除当前线程绑定的数据}}public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {} // 返回一个新的ThreadLocal变量/*** 外部使用不到的方法*/protected T initialValue() { return null; } // 初始值nullprivate static int nextHashCode() { } // hash值加上0x61c88647并且将加完后的值返回private T setInitialValue() {} // 返回null并设置初始值null和set方法一样的逻辑,只是value为nullThreadLocalMap getMap(Thread t) { } // 得到线程t绑定的ThreadLocalMapvoid createMap(Thread t, T firstValue) {} // 创建ThreadLocalMap并添加key=t,value=firstValue到map中并绑定到当前线程boolean isPresent() {} // 是否存有数据,有则返回true、没有则返回falsestatic 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); }@Overrideprotected 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; // 扩容条件默认 10static 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变为1setThreshold(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() {} // 再hashprivate Entry getEntry(ThreadLocal<?> key) {} // 根据key获取Entryprivate 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所对应的数据被移除返回trueprivate void expungeStaleEntries() {} // 删除所有过时元素}}
使用场景
ThreadLocal的使用场景
- springmvc的拦截器传参到控制层
- 一个参数要传入多个方法
- spring使用ThreadLocal来实现事务的控制,保证一次事务的所有操作需要在同一个数据库链接上
- 。。。
关于ThreadLocal的内存泄露
面试题
1.为什么要把ThreadLocal作为key,而不是Thread作为Key?这样不是更清晰吗
答:你提出的做法实际上就是所有的线程都访问ThreadLocal的Map,而key是当前线程,但是这有一个问题,一个线程可以有多个私有变量,那如果key是当前线程的话,意味着还要坐点手脚来唯一表示set进去的value,并且还要一个问题,当并发足够大时,意味着所有的线程都去操作同一个map,map体积会膨胀,导致访问性能下降,而且这个map维护所有线程私有变量,以为这你不知道什么时候可以销毁
