ThreadLocal到底是个什么东西?

作者: 我爱纽约先生 | 来源:发表于2019-12-08 22:37 被阅读0次
    Threads

    大家在学习Android原代码的时候,经常会看到ThreadLocal这个东西。这个东西日常开发的时候用的不多,有点点神秘。今天这篇文章就帮你揭开ThreadLocal的面纱,了解它的真相。


    对ThreadLocal最简单的理解

    从使用者的角度,可以把ThreadLocal简单看成是一个Map:Key的类型是Thread,Value是任意你想要保存的值。类似这样:Map<Thread, Object>

    可是,如果说ThreadLocal是个Map<Thread, Object>类型的话,那它提供的接口应该是这样的:

    void set(Object key, Object value)
    Object get(Object key)
    

    而实际上ThreadLocal的接口是这样的:

    void set(Object value)
    Object get()
    

    看到没?和普通map的读写接口不一样:ThreadLocal的读写接口里面并没有传key值!既然没有传key,那对于一个map来说,该如何读写value呢?

    其实,ThreadLocal的这个map有点特殊,它不允许随便传一个Thread对象作为Key值来访问,只能传递当前的线程对象。而当前的线程对象可以通过静态方法Thread.currentThread();来获得,所以就不用传key这个参数了。

    用代码总结下刚才的逻辑:

    public void set(Object value){
        Thread t = Thread.currentThread(); //获得当前线程
        map.set(t, value); //以当前线程为key,设置value
    }
    
    public Object get(){
        Thread t = Thread.currentThread(); //获得当前线程
        return map.get(t); //以当前线程为key,获得value
    }
    

    以上只是对ThreadLocal最简单的理解,如果你不追求实现细节的话,这基本也够了,如果你想了解更多的话,请接着看下面的内容。


    更深入一点了解ThreadLocal

    在我们没有阅读ThreadLocal源码的时候,我以为ThreadLocal一定是里面维护着一个map对象,保存着所有Thread对象和Value的值,如下图所示:

    我想象中的ThreadLocal的实现方式

    可是,在我读过源码之后,才发现:我想错了。

    实际上,ThreadLocal本身并没有存储任何对象,所有的东西都存储在Thread对象本身里!

    首先,在Thread对象(注意,不是ThreadLocal!)里有个成员变量:

    ThreadLocal.ThreadLocalMap threadLocals = null;

    这个成员变量的类型是ThreadLocal.ThreadLocalMap,这是ThreadLocal的一个内部类,可以理解为一个简单的HashMap(关于这个内部类的实现本篇就不详细展开了,有兴趣的同学可以看源码),就是保存键值对的一个容器。

    其实,所有set到ThreadLocal上的对象,实际都保存在当前Thread中的threadLocals成员变量里。

    为了说的清楚,下面我用伪代码的形式,写一下ThreadLocal的Set()方法的核心逻辑:

    public void set(T value) {
            Thread t = Thread.currentThread(); //先获得当前线程
            if (t.threadLocals != null){ //判断当前线程上的threadLocals成员是否为空
                t.threadLocals.set(this, value); //如果threadLocals不等于空,则set value,ThreadLocal对象作为key值。
            }else{
                t.threadLocals = new ThreadLocalMap(); //如果threadLocals为空,则创建之然后再set value。
                t.threadLocals.set(this, value); 
            }
        }
    

    通过以上代码大家应该都能看清楚,真正的value值是保存在Thread对象上的,同时,key值是ThreadLocal对象。

    再看看get方法,依然是只有核心逻辑的伪代码:

    public T get() {
            Thread t = Thread.currentThread(); //先获得当前线程
            if (t.threadLocals != null) { //判断当前线程上的threadLocals成员是否为空
                return (T)t.threadLocals.get(this); //不为空,则以ThreadLocal对象作为key值得到value,然后返回。
            }
            return setInitialValue(); //返回初始值
        }
    

    理清了get和set方法,相信整个ThreadLocal的实现方式,大家应该都比较清楚了。

    总之,ThreadLocal的存储方式,大概是这个样子:


    ThreadLocal的存储结构

    为什么如此实现?

    作者之所以这样设计,我相信是经过了充分考虑的。我能想到的一点,就是考虑了对象的生命周期。

    ThreadLocal要为每个线程维护value值,所以它的生命周期一般是长于线程对象生命周期的。一个长生命周期的对象维护一个短生命周期对象的引用,就有可能内存泄露,除非开发者要手动remove掉。

    而把value存放在Thread上,一切就可以完美的解决。因为这个value对象是专门为Thread对象而存在的,所以value对象和Thread对象的生命周期应该完全一致。把value对象存放在Thread对象里,则不会有内存泄露的风险。虽然,Thread会维护一个ThreadLocal的引用(实际上是个弱引用),但是如前所说,一个短生命周期的对象维护长生命周期对象的引用,一般没什么问题。

    几种对象生命周期长度比较

    以上就是我对ThreadLocal的一些简单理解,欢迎大家指正。

    相关文章

      网友评论

        本文标题:ThreadLocal到底是个什么东西?

        本文链接:https://www.haomeiwen.com/subject/zrfsgctx.html