美文网首页
Looper场景ThreadLocal原理分析

Looper场景ThreadLocal原理分析

作者: gczxbb | 来源:发表于2018-05-23 16:50 被阅读1次

    ThreadLocal介绍

    ThreadLocal支持泛型,ThreadLocal<T>,T代表的是线程本地变量,在多线程并发中,每一个线程都保存一份属于自己的T对象。任何线程对T的操作仅仅是本地操作,不会相互影响。
    参考Android-Looper源码中定义的ThreadLocal<Looper>。

    public final class Looper {
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
        ...
        private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    
        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
         ...
    }
    

    在Looper类中,定义了一个静态ThreadLocal<Looper>对象,可以利用set和get方法设置和获取Looper对象。我们都知道,Android消息机制是基于Looper的一个无限循环方法loop(),主线程或其他线程在此处休眠等待消息。loop是一个静态方法,它在执行时一定会拿到当前线程的本地Looper对象进,并获取Looper的西消息队列,因此,每一个线程都有自己独立的Looper对象,ThreadLocal可以实现线程变量的本地化。


    ThreadLocal如何实现本地变量

    先分析一下ThreadLocal的set/get方法,看一下内部如何存储对象。
    ThreadLocal#set方法。

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    

    某线程创建一个Looper对象,根据当前线程获取Thread内部ThreadLocal.ThreadLocalMap对象,ThreadLocalMap中有一个Entry数组,Entry是键值对key与value的封装,key就是ThreadLocal对象,value就是当前线程存储Looper对象。Entry数组负责存储线程本地变量。
    每一个线程Thread对象的ThreadLocalMap不同,ThreadLocal对象的作用就是提供一个key索引,索引对应的value就是其泛型指定的对象类型,如果有多个本地变量,那么再定义一些ThreadLocal<T>对象即可。

    static class Entry extends WeakReference<ThreadLocal> {
        Object value;
        Entry(ThreadLocal k, Object v) {
            super(k);
            value = v;
        }
    }
    

    Entry继承WeakReference,Entry对key键ThreadLocal对象的引用是弱引用。当遇到一个变量是线程本地变量时,创建一个ThreadLocal对象,Entry数组的key值弱引用reference指向ThreadLocal对象。
    ThreadLocal#get方法。

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
             if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
    
    当前线程get方法查找本地变量时,从Thread对象中找到ThreadLocalMap,从Map的Entry数组中根据ThreadLocal查找对应的Entry,每一种本地变量存储一个Entry。最后读取Entry的value。线程本地变量存储结构图如下。 利用ThreadLocal实现线程本地变量存储结构.jpg

    总结
    ThreadLocal实现线程本地变量的本质是将线程本地对象存储在当前Thread的ThreadLocalMap内部Entry数组,以键值对的形式进行存储,而键值对中的key正是ThreadLocal对象,对其引用是弱引用,value代表某一变量的对象。因此,ThreadLocal仅仅是作为一个 key让线程来获取value,存储位置在当前Thread的Map中。

    Looper内部ThreadLocal对象是静态的,因此,所有Looper对象公用一份ThreadLocal,每一个线程保存的key是相同的对象,而创建Looper对象value是不同的,对每个线程来说,value是隔离的就可以,并且非静态的还会多创造很多对象,增加内存消耗。
    有一个问题,为什么Map对ThreadLocal的引用是弱引用呢?个人理解,一般情况下,如果没有其他强引用,弱引用的对象会被垃圾回收,因此,若ThreadLocal对象没有了强引用,就很容易被回收掉,这时,Entry的key就是空值,从Thread到Map再到Entry的value有一条强引用链,导致value内存泄露,这种情况下只要线程对象一直存在,没有key的value就会一直存在,这条链已经无用,无法定位到value,导致内存泄露。
    如果Map对ThreadLocal使用的强引用,若ThreadLocal对象没有了其他强引用,那么,这条从Thread到Entry的强引用链会伴随Thread对象一直存在,因为已经无法从其他途径找到ThreadLocal对象啦,谈何获取Entry的key和value呢?因此,强引用的后果是整个Entry对象导致内存泄露。所以还是用弱引用比较稳妥,即使只有key这一条链访问到它(ThreadLocal),因为是弱引用链,至少还能保证被回收,我们只需要手动清除value即可。
    上面都提到,前提ThreadLocal对象已经没有了除了key值以外的强引用,在Looper中,ThreadLocal对象是作为静态对象存储的,每一个线程产生的Looper对象公有ThreadLocal对象,总会有Looper对象链路到ThreadLocal,这应该就是讲ThreadLocal设置static的另一个原因吧。其他的场景后续再分析。


    任重而道远

    相关文章

      网友评论

          本文标题:Looper场景ThreadLocal原理分析

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