美文网首页
Android-ThreadLocal

Android-ThreadLocal

作者: 有腹肌的豌豆Z | 来源:发表于2020-08-19 13:26 被阅读0次

    简介

    threadlocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。
    ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。

    使用方法

    static final ThreadLocal<T> sThreadLocal = new ThreadLocal<T>();
    sThreadLocal.set()
    sThreadLocal.get()
    

    set()

    public void set(T value) {
          //获取当前线程
          Thread t = Thread.currentThread();
          //实际存储的数据结构类型
          ThreadLocalMap map = getMap(t);
          //如果存在map就直接set,没有则创建map并set
          if (map != null)
              map.set(this, value);
          else
              createMap(t, value);
      }
    

    getMap()

    ThreadLocalMap getMap(Thread t) {
          //thred中维护了一个ThreadLocalMap
          return t.threadLocals;
     }
    

    createMap

    void createMap(Thread t, T firstValue) {
          //实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals
          t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    

    get()

    public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    

    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;
        }
    

    initialValue()

    // 
    protected T initialValue() {
            return null;
        }
    

    remove()

    // 内存泄漏
    public void remove() {
            ThreadLocalMap m = getMap(Thread.currentThread());
            if (m != null)
                m.remove(this);
        }
    

    ThreadLocalMap

    Entry

    //Entry为ThreadLocalMap静态内部类,对ThreadLocal的若引用
    //同时让ThreadLocal和储值形成key-value的关系
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
               super(k);
                value = v;
        }
    }
    

    ThreadLocalMap构造函数

    • ThreadLocalMap是ThreadLocal 静态内部类
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            // private Entry[] table;       
            //内部成员数组,INITIAL_CAPACITY值为16的常量
            table = new Entry[INITIAL_CAPACITY];
            //位运算,结果与取模相同,计算出需要存放的位置
            //threadLocalHashCode比较有趣
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
    }
    
    ThreadLocal特性

    ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是。

    • Synchronized是通过线程等待,牺牲时间来解决访问冲突
    • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
    • ThreadLocal的线程隔离特性

    内存泄漏

    ThreadLocal为什么会内存泄漏
    • ThreadLocal在ThreadLocalMap中是以一个弱引用身份被Entry中的Key引用的,因此如果ThreadLocal没有外部强引用来引用它,那么ThreadLocal会在下次JVM垃圾收集时被回收。这个时候就会出现Entry中Key已经被回收,出现一个null Key的情况,外部读取ThreadLocalMap中的元素是无法通过null Key来找到Value的。因此如果当前线程的生命周期很长,一直存在,那么其内部的ThreadLocalMap对象也一直生存下来,这些null key就存在一条强引用链的关系一直存在:Thread --> ThreadLocalMap-->Entry-->Value,这条强引用链会导致Entry不会回收,Value也不会回收,但Entry中的Key却已经被回收的情况,造成内存泄漏。
    • 但是JVM团队已经考虑到这样的情况,并做了一些措施来保证ThreadLocal尽量不会内存泄漏:在ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value,并将整个Entry设置为null,利于下次内存回收。
    为什么使用弱引用?
    • 从表面上看,发生内存泄漏,是因为Key使用了弱引用类型。但其实是因为整个Entry的key为null后,没有主动清除value导致。很多文章大多分析ThreadLocal使用了弱引用会导致内存泄漏,但为什么使用弱引用而不是强引用?

    下面我们分两种情况讨论:

    • key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
    • key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
      比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

    因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key的value就会导致内存泄漏,而不是因为弱引用。

    那么怎么避免内存泄漏呢?
    • 每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

    相关文章

      网友评论

          本文标题:Android-ThreadLocal

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