美文网首页
Android-ThreadLocal

Android-ThreadLocal

作者: 写代码的阿毛 | 来源:发表于2020-02-16 12:30 被阅读0次

    概述

    • ThreadLocal用于保存线程作用域内的对象;对象是线程共享的,所以为了达到线程之间使用不同的对象,使用者不能将对象赋值给类变量或者实例变量,只能赋值给局部变量,ThreadLocal对象无限制,但是ThreadLocal需要static(一般为static final或者private static,但是禁止外部方法重复创建ThreadLocal对象);
    • 主要涉及ThreadLocal和ThreadLocalMap两个类,简单说下两个类的作用,方便源码解析;ThreadLocal对象相当于一份数据在不同线程的数据对象的集合,通过ThreadLocal获取数据时,只能获取到当前线程对应的数据;ThreadLocalMap相当于一个线程内所有的ThreadLocal对象在该线程内的数据对象Map,key是ThreadLocal的hashCode(定制hashCode,并非Object.hashCode生成);ThreadLocalMap外部不能访问,只能通过ThreadLocal获取数据;
    • 很多SDK源码或者第三方库中都用到了ThreadLocal,比如Looper对象就是保存在ThreadLocal中;
    • 源码是基于Android-SDK-29;

    源码

    ThreadLocalMap
    • ThreadLocalMap相当于单线程作用域的Map,是所有ThreadLocal对象在本线程的数据的Map集合;
    • ThreadLocalMap是存储在Thraed对象中,也就确保了一个线程对应了一个ThredLocalMap,ThreadLocalMap里存储的都是本线程对应的所有数据;
    • ThreadLocal.ThreadLocalMap.Entry
      static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
          super(k);
          value = v;
        }
      }
      
      • Entry类似于HashMap中的Entry,用来保存真正的数据;
      • Entry继承于WeakReference<ThreadLocal>,如果ThreadLocal(可以理解成对数据的封装了一层,get/set都是和当前相关联的数据)被回收了(比如static的ThreadLocal被重新赋值并触发GC,原来的ThreadLocal对象就会被回收),但是Entry对象或者数据并没有回收,在相关方法调用时做后续处理;
    • ThreadLocal.ThreadLocalMap
      • ThreadLocalMap的数据结构是数组,数组元素是Entry,根据ThreadLocal对象的hashCode和数组长度求模算出对应的索引,如果数组对应的引用为null,则直接存储,否则,循环索引递增求模,直到数组对应索引为null存储;
      • ThreadLocalMap有个很重要的概念,源码中是 run ,可以理解成“链”,即数组中连续不为null的部分称为“链”;
      • ThreadLocalMap.set
        private void set(ThreadLocal<?> key, Object value) {
                // We don't use a fast path as with get() because it is at
                // least as common to use set() to create new entries as
                // it is to replace existing ones, in which case, a fast
                // path would fail more often than not.
        
                Entry[] tab = table;
                int len = tab.length;
                //ThreadLocal对象的hashCode(定制)和数组长度求模,得到数组的索引;
                int i = key.threadLocalHashCode & (len-1); 
                //从索引开始递增,直到索引对应的引用为null
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    ThreadLocal<?> k = e.get();
                    //ThreadLocal对象为static,所以直接用 == 做判断
                    if (k == key) {
                        //更新已存在的数据
                        e.value = value;
                        return;
                    }
                    //ThreadLocal对象已被回收
                    if (k == null) {
                        //替换Entry并清除已回收的ThreadLocal
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
                //新创建Entry对象,放在末尾处
                tab[i] = new Entry(key, value);
                int sz = ++size;
                //清除已回收ThreadLocal
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash(); //扩容
            }
        
        • 1.根据ThreadLocal的hashCode(定制hashCode)求模,算出对应的索引;
        • 2.循环索引递增,如果Entry中的ThreadLocal匹配,则更新数据,如果Entry中ThreadLocal已被回收,则替换Entry并删除其他被回收的Entry;
        • 3.如果没有找到Entry,则创建新的Entry并放在链的末尾;
      • ThreadLocalMap.getEntry
        private Entry getEntry(ThreadLocal<?> key) {
                int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                if (e != null && e.get() == key)
                    return e; //根据求模获取的索引,直接命中
                else
                    return getEntryAfterMiss(key, i, e); //在链中寻找
            }
        
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
                Entry[] tab = table;
                int len = tab.length;
                //循环迭代直到链结束
                while (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == key)
                        return e; //ThreadLocal匹配,直接返回
                    if (k == null)
                        expungeStaleEntry(i); //从i开始到链结束,删除已回收ThreadLocal,未被回收的重新求模存储;
                    else
                        i = nextIndex(i, len);
                    e = tab[i];
                }
                return null;
            }
        
      • ThreadLocalMap.remove
        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();
                        //从i开始循环当前链,删除已回收ThreadLocal,未被回收的重新求模存储;
                        expungeStaleEntry(i);
                        return;
                    }
                }
            }
        
      • ThreadLocalMap.resize
        private void rehash() {
                //删除所有已被回收的ThreadLocal,并重新求模存储
                expungeStaleEntries();
                // Use lower threshold for doubling to avoid hysteresis
                if (size >= threshold - threshold / 4) //超过阈值的3/4,就扩容;阈值是数组长度的2/3;
                    resize(); //扩容
            }
        private void resize() {
                Entry[] oldTab = table;
                int oldLen = oldTab.length;
                int newLen = oldLen * 2; //2倍扩容
                Entry[] newTab = new Entry[newLen];
                int count = 0;
        
                for (int j = 0; j < oldLen; ++j) {
                    Entry e = oldTab[j];
                    if (e != null) {
                        ThreadLocal<?> k = e.get();
                        if (k == null) {
                            e.value = null; // Help the GC,处理已被回收的ThreadLocal
                        } else {
                            //重新求模存储
                            int h = k.threadLocalHashCode & (newLen - 1);
                            while (newTab[h] != null)
                                h = nextIndex(h, newLen);
                            newTab[h] = e;
                            count++;
                        }
                    }
                }
        
                setThreshold(newLen);
                size = count;
                table = newTab;
            }
        
        • 扩容是按照原来数组长度的两倍扩容;触发扩容是大于等于3/4阈值,阈值是数组长度的2/3,触发扩容是数组长度的1/2;
    ThreadLocal
    • ThreadLocal对象可以理解为数据对象在不同线程中的备份的集合;ThreadLocal对象是static,才能确保线程内唯一;
    • hashCode
      //当前ThreadLocal对象的hashCode
      private final int threadLocalHashCode = nextHashCode();
      //静态变量,存储最后一个创建的ThreadLocal对象的hashCode
      private static AtomicInteger nextHashCode = new AtomicInteger();
      //ThreadLocal对象之间hashCode的间隔
      private static final int HASH_INCREMENT = 0x61c88647;
      //用于生成下一个ThreadLocal对象的hashCode
      private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
      }
      
      • ThreadLocal对象的hashCode是定制的,对象之间的hashCode是固定间隔的,对于同一个ThreadLocal的不同线程之间的备份数据对象,由于是同一个ThreadLocal对象,所以hashCode是一样的;
    • ThreadLocal.get
      public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                //获取ThreadLocalMap中ThreadLocal对应的Entry
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    //本线程对应的数据
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
      
      ThreadLocalMap getMap(Thread t) {
            return t.threadLocals; //Thread中的ThreadLocalMap变量
        }
      
    • ThreadLocal.set
      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;
        }
      
    • ThreadLocal.remove
      public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
      

    总结

    • 只有在线程内唯一实例的时候,才需要用ThreadLoal,ThreadLocal变量要static,并且确保同一份数据(对于业务层)只有一个ThreadLocal对象;
    • 数据本身不要被类变量或者实例变量引用,只有在方法内部使用的时候,通过ThreadLocal获取;否则,不同线程的数据对象还是可以被其他线程访问;

    相关文章

      网友评论

          本文标题:Android-ThreadLocal

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