硬核剖析ThreadLocal源码,面试官看了直呼内行( 三 )

再看一下具体的遍历Entry数组的逻辑:
// 具体的遍历Entry数组的方法private Entry getEntry(ThreadLocal<?> key) {// 通过hashcode计算数组下标位置int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];// 如果下标位置对象不为空 , 并且等于当前ThreadLocal实例对象 , 直接返回if (e != null && e.get() == key)return e;else// 如果不是 , 需要继续向下遍历Entry数组return getEntryAfterMiss(key, i, e);}再看一下线性探测法特殊的取值方法:
// 如果不是 , 需要继续向下遍历Entry数组private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;// 循环遍历数组 , 直到找到ThreadLocal对象 , 或者遍历到数组为空的位置while (e != null) {ThreadLocal<?> k = e.get();// 如果等于当前ThreadLocal实例对象 , 表示找到了 , 直接返回if (k == key)return e;// key是null , 表示ThreadLocal实例对象已经被GC回收 , 就帮忙清除valueif (k == null)expungeStaleEntry(i);else// 索引位置+1 , 表示继续向下遍历i = nextIndex(i, len);e = tab[i];}return null;}// 索引位置+1 , 表示继续向下遍历 , 遍历到数组结尾 , 再从头开始遍历private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}ThreadLocal的get方法流程如下:

硬核剖析ThreadLocal源码,面试官看了直呼内行

文章插图
4.4 remove方法源码remove方法流程跟set、get方法类似 , 都是遍历数组 , 找到ThreadLocal实例对象后 , 删除key、value , 再删除Entry对象结束 。
public void remove() {// 获取当前线程的ThreadLocalMap对象ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}// 具体的删除方法private void remove(ThreadLocal<?> key) {ThreadLocal.ThreadLocalMap.Entry[] tab = table;int len = tab.length;// 计算数组下标int i = key.threadLocalHashCode & (len - 1);// 遍历数组 , 直到找到空位置 , // 或者值等于当前ThreadLocal对象 , 才结束for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {// 找到后 , 删除key、value , 再删除Entry对象if (e.get() == key) {e.clear();expungeStaleEntry(i);return;}}}5. ThreadLocal使用注意事项使用ThreadLocal结束 , 一定要调用remove方法 , 清理掉threadLocal数据 。具体流程类似下面这样:
/** * @author 一灯架构 * @apiNote ThreadLocal示例 **/public class ThreadLocalDemo {// 1. 创建ThreadLocalstatic ThreadLocal<User> threadLocal = new ThreadLocal<>();public void method() {try {User user = getUser();// 2. 给threadLocal赋值threadLocal.set(user);// 3. 执行其他业务逻辑doSomething();} finally {// 4. 清理threadLocal数据threadLocal.remove();}}}如果忘了调用remove方法 , 可能会导致两个严重的问题:
  1. 导致内存溢出
    如果线程的生命周期很长 , 一直往ThreadLocal中放数据 , 却没有删除 , 最终产生OOM
  2. 导致数据错乱
    如果使用了线程池 , 一个线程执行完任务后并不会被销毁 , 会继续执行下一个任务 , 导致下个任务访问到了上个任务的数据 。
6. 常见面试题剖析看完了ThreadLocal源码 , 再回答几道面试题 , 检验一下学习成果怎么样 。
6.1 ThreadLocal是怎么保证数据安全性的?ThreadLocal底层使用的ThreadLocalMap存储数据 , 而ThreadLocalMap是线程Thread的私有变量 , 不同线程之间数据隔离 , 所以即使ThreadLocal的set、get、remove方法没有加锁 , 也能保证线程安全 。

经验总结扩展阅读