Java ThreadLocal 源码解读

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_32197439/article/details/82284203

ThreadLocal 源码解读

  • 之前对于ThreadLocal的用法和原理都没有深入的理解,因此准备学习一番有关于ThreadLocal的知识。

ThreadLocal是什么?

  • 它为线程提供了线程本地的变量,每个线程都保存有一个变量副本,使得同一个线程在任何时刻访问它的时候都是一致的。它的生命周期跟线程绑定在一起,线程结束生命周期,该变量副本也会被GC。简单说 threadlocal提供了一个线程隔离与线程绑定。通常定义为 private static 类型

主要变量及方法 ##

  • 变量
    • threadLocalHashCode
private final int threadLocalHashCode = nextHashCode();
  • 通过此变量保证threadLocal的唯一性,并通过CAS操作更新,增量为 HASH_INCREMENT = 0x61c88647
  • 方法
  • 构造函数
    • ThreadLocal threadLocal = new ThreadLocal<>();
  • set()
public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
        // map为空则创建
            createMap(t, value);
    }

    // thread 中保存实例成员变量tl
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
  • threadLocalMap
    • Entry[] table // entry数组 用于存储数据 Entry(ThreadLocal
private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
  • cleanSomeSlots清理
  • get()
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
  • ThreadLocalMap.getEntry()
    • hash以后如果是ThreadLocal对应的Entry就返回,没有找到就调用getEntryAfterMiss()线性探测寻找 return entry || null
  • remove()
public void remove() {
        // 获取当前线程维护的threadlocalmap
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

    private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    // remove的时候同样也会调用expungeStaleEntry方法执行清理工作
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

小结

  • 每个Thread里都含有一个ThreadLocalMap的成员变量,这种机制将ThreadLocal和线程巧妙地绑定在了一起,即可以保证无用的ThreadLocal被及时回收,不会造成内存泄露,又可以提升性能。
  • 假如我们把ThreadLocalMap做成一个Map,那么它存储的东西将会非常多(相当于一张全局线程本地变量表),这样的情况下用线性探测法解决哈希冲突的问题效率会非常差。而JDK里的这种利用ThreadLocal作为key,再将ThreadLocalMap与线程相绑定的实现,完美地解决。
  • 关于GC
    • 无用 Entry 什么时候会被清理
      • 线程结束
      • 调用ThreadLocalMap的remove方法或set(null)时
      • 插入元素时,发现staled entry,则会进行替换并清理
      • 插入元素时,map大小达到阈值,并且没有任何staled entries的时候,会调用rehash方法清理并扩容
  • 应用场景
    • 实现单个线程单例以及单个线程上下文信息存储,比如交易id等
    • 实现线程安全,非线程安全的对象使用ThreadLocal之后就会变得线程安全,因为每个线程都会有一个对应的实例
    • 承载一些线程相关的数据,避免在方法中来回传递参数
    • 在线程池中 Thread不关闭 一般都会调用remove移除
  • 空间换时间思想
  • InheritableThreadLocal 能够复制父类的数据
  • 判断内存泄露的标准应该是 一个对象不再需要了,却无法回收依然存在才可以称之为内存泄漏,
  • 参考

猜你喜欢

转载自blog.csdn.net/sinat_32197439/article/details/82284203