ThreadLocal

WeakReference

弱引用对象相对软引用对象具有更短暂的生命周期,只要 G C 发现它仅有弱引用,不管内存空间是否充足,都会回收它,不过 G C 是一个优先级很低的线程,因此不一定会很快发现那些仅有弱引用的对象。

只有对象仅被 WeakReference 引用,它才是弱引用级别对象,因为对象可以在多处被引用,所以 WeakReference 引用的对象,它可能在其他处被强引用了。

PhantomReference

虚引用形同虚设,与其他几种引用不同,虚引用不会决定对象的生命周期

如果一个对象仅有虚引用,那它就和没有任何引用一样,任何时候都可能被 G C 回收。

与弱引用的区别

  1. SoftReference、WeakReference引用的对象没被回收时,可以使用get方法获取真实对象地址
  2. PhantomReference使用get方法永远返回null

简单说就是「无法通过虚引用来获取对象的真实地址」

引用队列

Java中SoftReference、WeakReference、PhantomReference,可以理解为对象引用级别包装类,在项目中使用对应的包装类,赋予对象引用级别。

ReferenceQueue引用队列是配合对象引用级别包装类(SoftReference、WeakReference、PhantomReference)使用,当对象引用级别包装类所指向的对象,被垃圾回收后,该对象引用级别包装类被追加到引用队列,因此可以通过引用队列做 G C 相关统计或额外数据清理等操作。

ThreadLocal是什么

Thread类声明了成员变量threadLocals,threadLocals才是真正的线程本地变量,因此每个 Thread 都有自己的线程本地变量,所以线程本地变量拥有线程隔离特性,也就是天生的线程安全。

public class Thread implements Runnable {
    //线程本地变量
    ThreadLocalMap threadLocals = null;

    ThreadLocalMap inheritableThreadLocals = null;
}    

可以看到 threadLocals 成员变量类是 ThreadLocal.ThreadLocalMap,即是 ThreadLocal 提供的内部类,因此 Thread 线程本地变量的创建、新增、获取、删除实现核心,必然是围绕 threadLocals,所以开发者也是围绕 threadLocals 实现功能,为了后续重复使用,还会对代码实现进行封装复用,而 ThreadLocal 就是线程本地变量工具类,由 J D K 提供,线程本地变量的功能都已经实现好了,开箱即用,造福广大开发人员。

ThreadLocal常用的方法

  1. set:为当前线程设置变量,当前ThreadLocal作为索引
  2. get:获取当前线程变量,当前ThreadLocal作为索引
  3. initialValue(钩子方法需要子类实现):懒加载形式初始化线程本地变量,执行get时,发现线程本地变量为null,就会执行initialValue的内容
  4. remove:清空当前线程的ThreadLocal索引与映射的元素

一个 Thread可以拥有多个 ThreadLocal键值对(存储在ThreadLocalMap结构),又因为 ThreadLocalMap 依赖当前Thread,Thread销毁时 ThreadLocalMap 也会随之销毁,所以 ThreadLocalMap 的生命周期与 Thread 绑定

现在总结出「本地线程变量的作用域,属于当前线程整个范围,一个线程可以跨越多个方法使用本地线程变量」,当你希望某些变量在某 Thread 的多个方法中共享 并保证线程安全,那就大胆的使用ThreadLocal。

(ps:一定要想清楚,是某个变量被Thread生命周期内多个方法共享,还是多个Thread共享这个变量!)

源码

ThreadLocalMap结构

static class ThreadLocalMap {
        // 默认数组长度
        private static final int INITIAL_CAPACITY = 16;
        // 数组
        private ThreadLocal.ThreadLocalMap.Entry[] table;
        private int size;
        private int threshold;

        private void setThreshold(int var1) {
            this.threshold = var1 * 2 / 3;
        }

        private static int nextIndex(int var0, int var1) {
            return var0 + 1 < var1 ? var0 + 1 : 0;
        }

        private static int prevIndex(int var0, int var1) {
            return var0 - 1 >= 0 ? var0 - 1 : var1 - 1;
        }

        ThreadLocalMap(ThreadLocal<?> var1, Object var2) {
            this.size = 0;
            this.table = new ThreadLocal.ThreadLocalMap.Entry[16];
            int var3 = var1.threadLocalHashCode & 15;
            this.table[var3] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);
            this.size = 1;
            this.setThreshold(16);
        }
        // Entryu对象继承了WeakReference
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> var1, Object var2) {
                super(var1);
                this.value = var2;
            }
        }
    }

get方法

    public T get() {
        // 获取当前线程
        Thread var1 = Thread.currentThread();
        // 获取当前线程本地变量,getMap(t)= t.threadLocals
        ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
        if (var2 != null) {
            // 获取Entry
            ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
            if (var3 != null) {
                // 获取value
                Object var4 = var3.value;
                return var4;
            }
        }
        // 如果本地线程变量为null或者value为空,执行初始化value
        return this.setInitialValue();
    }

initialValue方法

    private T setInitialValue() {
        //执行initialValue方法获取value
        Object var1 = this.initialValue();
        //获取当前线程
        Thread var2 = Thread.currentThread();
        //获取线程本地变量threadLocals
        ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
        if (var3 != null) {
            //本地线程变量不为空时,当前ThreadLocal作为所有设置映射的value
            var3.set(this, var1);
        } else {
            //如果线程本地变量为空,创建线程本地变量,并把当前TreadLocal作为索引映射为value
            this.createMap(var2, var1);
        }
        //返回value
        return var1;
    }

set设置变量

    public void set(T var1) {
        //获取当前线程
        Thread var2 = Thread.currentThread();
        //获取线程本地变量threadLocals
        ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
        if (var3 != null) {
            //本地变量不为空,当前ThreadLocal为索引设置映射的value
            var3.set(this, var1);
        } else {
            //本地变量为空,创建线程本地变量,当前ThreadLocal为所有设置映射的value
            this.createMap(var2, var1);
        }
    }

remove清楚变量

        private void remove(ThreadLocal<?> var1) {
            //获取Entry数组
            ThreadLocal.ThreadLocalMap.Entry[] var2 = this.table;
            int var3 = var2.length;
            //计算出当前ThreadLocal的数组下标
            int var4 = var1.threadLocalHashCode & var3 - 1;

            for(ThreadLocal.ThreadLocalMap.Entry var5 = var2[var4]; var5 != null; var5 = var2[var4 = nextIndex(var4, var3)]) {
                //遍历,直到找到Entry中key为当前ThreadLocal的那个元素
                if (var5.get() == var1) {
                    //清楚该元素
                    var5.clear();
                    this.expungeStaleEntry(var4);
                    return;
                }
            }

        }

小结

核心就三样:ThreadLocal线程本地变量工具类(同时作为索引)、Entry基本元素(由弱引用包装类ThreadLocal与value组成),Entry数组容器,到这里流程很清晰了,ThreadLocal计算出数组索引,用 ThreadLocal 与 value 构建出 Entry 元素,最终放入 Entry 容器中。

remove背后的意义

大伙都知道 Entry 中对 ThreadLocal 使用弱引用,但value是强引用,如果消除 ThreadLocal 强引用,value值无法清理,最终内存溢出。

其实value作为强引用设计属于合理,如果用软或弱引用,就出大问题了,程序跑着跑着突然get到了一个null,估计都得骂娘了,所以为解决内存溢出问题 J D K提供remove方法,使开发人员可以选择手动清理整个Entry元素,防止内存溢出。

还记的之前说过吗?线程本地变量的生命周期与线程绑定,一般线程的生命周期比较短,线程结束时,线程本地变量自然就销毁了,软引用与 remove 会不会有点多余了?

业务瞬息万变,大部分情况来说线程的生命周期比较短,但也业务场景会导致线程的生命周期较长,甚至可能线程无限循环执行,这些是你没办法预料到的,数量一旦上来很容易内存溢出,所以个人建议使用完之后及时清理ThreadLocal,理由如下

  • 生命周期较长的线程场景
  • 无限循环线程的场景
  • 线程池场景(因为线程池可以复用线程,而且公司使用的框架可能会定制化线程池,你不能保证他会在线程池内帮你remove)

转载:https://mp.weixin.qq.com/s?__biz=MzAwMDg2OTAxNg==&mid=2652048380&idx=1&sn=d4420022dee3f10a39cbc8ca24fcf955&scene=21#wechat_redirect


   转载规则


《ThreadLocal》 锦泉 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录