美文网首页
ThreadLocal

ThreadLocal

作者: JackyWu15 | 来源:发表于2019-12-24 15:42 被阅读0次

    在分析消息机制时,我们知道安卓的Looper是通过使用ThreadLocal来存储,以保证每个线程都有自己唯一的一份,下面来分析ThreadLocal的源码

    public final class Looper {
        private static final String TAG = "Looper";
    
        // sThreadLocal.get() will return null unless you've called prepare().
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    

    先看以下ThreadLocal的使用方式:

     private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
    
        public  void test(){
            mBooleanThreadLocal.set( true );
            System.out.println(Thread.currentThread().getName()+">>"+mBooleanThreadLocal.get());
    
            new Thread("Thread1" ){
                @Override
                public void run() {
                    mBooleanThreadLocal.set( true );
                    System.out.println(Thread.currentThread().getName()+">>"+mBooleanThreadLocal.get());
                }
            }.start();
    
            new Thread("Thread2" ){
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+">>"+mBooleanThreadLocal.get());
                }
            }.start();
        }
    
    

    日志输出如下

    main>>true
    Thread1>>true
    Thread2>>null
    

    开始源码分析:

    public void set(T value) {
            //记录当前线程
            Thread t = Thread.currentThread();
          //获取Map
            ThreadLocalMap map = getMap(t);
          //Map不为null则存入,为null则新建
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
     }
    
    
    ThreadLocalMap getMap(Thread t) {
            //当前线程的成员变量threadLocals为ThreadLocalMap 
            return t.threadLocals;
        }
    

    可以看到,其实是 ThreadLocal 内部维护了一个 Map,每个线程首次使用都会创建自己的一个, 并保存到threadLocals这个变量中,上面示例第一次调用mBooleanThreadLocal.set( true )时,会执行到createMap

     void createMap(Thread t, T firstValue) {
            //ThreadLocalMap保存在当前线程的成员变量threadLocals中
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    
      static class ThreadLocalMap {
           //ThreadLocal为Key,值为Value,ThreadLocal为弱引用对象
            static class Entry extends WeakReference<ThreadLocal<?>> {
              //我们存入的值
               Object value;
               Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
            private static final int INITIAL_CAPACITY = 16;
           
            private Entry[] table;
          
            private int size = 0;
           
            private int threshold; 
            
            private void setThreshold(int len) {
                threshold = len * 2 / 3;
            }
        
            ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
              //构造一个Entry数组,长度默认16
                table = new Entry[INITIAL_CAPACITY];
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                //ThreadLocal,和我们设置的值,存入table数组
                table[i] = new Entry(firstKey, firstValue);
                size = 1;
                //如果容量超过16*2/3,就会重新分配table
                setThreshold(INITIAL_CAPACITY);
            }
    

    ThreadLocalMap已经创建完成,再来分析,同一线程,第二次调用时走的set方法

     private void set(ThreadLocal<?> key, Object value) {
    
                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)]) {
                     ThreadLocal<?> k = e.get();
                   //如果Entry 的ThreadLocal和现在用的是同一个,就直接将以前的数据覆盖掉
                    if (k == key) {
                        e.value = value;
                        return;
                    }
                  //有对象但是k == null(被回收了),调用 replaceStaleEntry(key, value, i)将数据存储进去
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                  //扩容
                    rehash();
            }
    

    看获取方法

    public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
              //获取Entry
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            //初始化一个ThreadLocalMap 
            return setInitialValue();
        }
    

    示例中Thread2没有先调用set,而是直接调用get,并且返回日志打印了null。此时的ThreadLocalMap ==null,将执行到setInitialValue方法,我们来看做了什么操作

     private T setInitialValue() {
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
     }
    
    protected T initialValue() {
            return null;
     }
    

    可以看到,initialValue()只返回了一个null值,接下的逻辑和上面的没有差别,构造了一个ThreadLocalMap ,把null存了进去,如果我们先调用了set存了值,那么走getEntry方法

     private Entry getEntry(ThreadLocal<?> key) {
                //通过哈希函数算出下标
                int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                //找到我们的ThreadLocal
                if (e != null && e.get() == key)
                    return e;
                else
                  //如果没找到,则调用 getEntryAfterMiss(key, i, e) 从当前节点开始线性查找。
                    return getEntryAfterMiss(key, i, e);
     }
    

    结论:

    • 每一个线程都有一个变量threadLocals,是一个ThreadLocalMap;
    • ThreadLocalMap的静态内部类Entry继承弱引用,封装了ThreadLocal为Key(包装为弱引用),Value为我们设置的值;
    • ThreadLocalMap用一个变量为table 的Entry数组,长度默认为16,来保存被封装的多种数据类型的Entry。

    即,当我们使用ThreadLocal,并设置数据时,会以ThreadLocal为Key,设置的数据为Value,封装成Entry,然后把这个Entry加入到变量为table的Entry[]数组中。

    相关文章

      网友评论

          本文标题:ThreadLocal

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