美文网首页
ThreadLocal精解

ThreadLocal精解

作者: AntCoding | 来源:发表于2019-04-03 21:02 被阅读0次

基础定义

  • [1] ThreadLocal提供了线程本地的实例.
  • [2] 同一个变量在每一个线程都会创建一个相对独立的实例副本,不同的线程可以访问自身线程内的副本变量
  • [3] ThreadLocal变量通常被private static 修饰,当一个线程结束时,它所使用的所有ThreadLocal相对的实例副本都可被回收.
  • [4] 当线程不需要操作ThreadLocal时 及时Remove掉 防止出现内存泄露

使用场景

ThreadLocal适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,即变量在线程间隔离而在方法或类间共享的场景

ThreadLocal源码解析

ThreadLocal set函数

存储当前线程下的变量副本

public void set(T value) {
    Thread t = Thread.currentThread(); //获取当前线程
 ​
    //[1]获取当前线程下的ThreadLocalMap对象​
    ThreadLocalMap map = getMap(t);
 
​   //做判定,若map不为null​
    if (map != null) {
​        //将value添加到 ThreadLocalMap​
        map.set(this, value);
​   }else{ //map 为 null
​
         //[2]创建新ThreadLocalMap实例​
        createMap(t, value);
  }​
}​
为什么这么简单方法我要标记出[1],[2]来分析呢?我们先将源码贴上
//获取当前线程下的ThreadLocalMap对象​
​ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
​
​​创建新ThreadLocalMap实例​
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}​​​

讲原因 因为这里存在 烟雾弹 ,我们在执行上述方法时传递的参数类型都是 Thread类型, 但是我们仔细看实现方法中 获取的都是 ThreadLocal类型 所以这里点明一下

ThreadLocal get函数

获取当前线程下的变量副本

public T get() {
    Thread t = Thread.currentThread(); //获取当前线程
    ThreadLocalMap map = getMap(t); //获取当前线程下ThreadLocal对象
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this); //通过key获取ThreadLocal的实体数据
        if (e != null) { //若不为null
            @SuppressWarnings("unchecked")
            T result = (T)e.value; //获取value 并返回
            return result;
        }
    }
    ​
    return setInitialValue();
}
//设置初始值​,执行步骤与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;
}​​
protected T initialValue() {
    return null;
}​

ThreadLocal remove函数

删除当前线程下的变量副本

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}
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();
            expungeStaleEntry(i);
            return;
        }
    }
}​

总结

通过源码我们能出其实ThreadLocal中只是封装了简单的 增删改查 的操作,实质性的关联操作都是在ThreadLocalMap 去完成,ThreadLocal的存在就像是ThreadLocalMap对外开放的一个 API一样的存在

ThreadLocalMap的源码分析

通过查看源码我们可知ThreadLocalMap就是一个 Map集合,在其内部有一个静态内部类Entry 用于存储 key-value的实体类

构造方法&常量属性

static class ThreadLocalMap {
    // Entry实体类,对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;
        }
    }
    /**
     *Map数组的初始化大小—— 必须是2的次幂
     */
    private static final int INITIAL_CAPACITY = 16;
    /**
     * 表对象,长度必须是2的次幂
     */
    private Entry[] table;
    /**
     *数组表的字条目数量
     */
    private int size = 0;
    /**
     * 下一次数组调整大小的值, 阈值
     */
    private int threshold; // Default to 0
    /**
     * 将调整大小阈值设置为最坏情况下保持2/3的负载因子。
     */
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }
    /**
     *三元运算 判定下一个Index若大于范围值  若大于范围值则返回0.
     */
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }
    /**
     * 三元运算 判定上一个Index是否小于等0  若小于等于0 则返回 len-1 索引下标 即最后一个下标值
     */
    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }
    /**
     * ThreadLocalMaps是延迟构造的,因此只创建至少包含一条数据的Map 
     *构造一个最初包含(firstKey, firstValue)的新映射
     */
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY]; //声明数组
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //key的hashCode 与 初始大小-1 做二进制与运算 获得 table下标
        table[i] = new Entry(firstKey, firstValue); //向table索引下赋一组Entry数据
        size = 1;
        setThreshold(INITIAL_CAPACITY);  //将调整大小阈值设置为最坏情况下保持2/3的负载因子
    }
    /**
     * 指定一个ThreadLocalMap集合,进行全部添加
     *
     * @param parentMap the map associated with parent thread.
     */
    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table;
        int len = parentTable.length;
        setThreshold(len);
        table = new Entry[len];
        for (int j = 0; j < len; j++) {
            Entry e = parentTable[j];
            if (e != null) {
                @SuppressWarnings("unchecked")
                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                if (key != null) {
                    Object value = key.childValue(e.value);
                    Entry c = new Entry(key, value);
                    int h = key.threadLocalHashCode & (len - 1);
                    while (table[h] != null)
                        h = nextIndex(h, len);
                    table[h] = c;
                    size++;
                }
            }
        }
    }
}​

ThreadLocalMap set函数解析

private void set(ThreadLocal<?> key, Object value) {
    //获取table,赋给方法内的私有变量
    Entry[] tab = table;
    int len = tab.length; //table长度
    int i = key.threadLocalHashCode & (len-1); //获取当前key的下标索引
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) { //从key的下标索引开始遍历循环
        ThreadLocal<?> k = e.get();
        if (k == key) { //当循环一圈结束后,又会回到key的下标索引位置,在此处判定 退出循环
            e.value = value;
            return;
        }
        //此处对k判空,因为k的类型引用为弱引用,容易被系统GC掉 k就为null
        //k为null,value则无用了,但由于value的引用类型为强引用,容易造成内存泄露​
        if (k == null) {
            //使用此方法,将索引下的数据​替换成新的key和value
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    tab[i] = new Entry(key, value); 
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold) //判断是否需要扩容
        rehash();//进行扩容操作
}

ThreadLocalMap get函数解析

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);//索引下标
    Entry e = table[i]; //通过索引下标获取table数组中e
   //做判定 e不为null &  e中的key值与key相等​
    if (e != null && e.get() == key) 
        return e;
    else  //判定失败
        //​​
        return getEntryAfterMiss(key, i, e);
}
​/**
  *遍历整个Map数组查找
  */​​​
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table; 
    int len = tab.length;
   //while条件 e不可为 null​
    while (e != null) {
        ThreadLocal<?> k = e.get();//获取e中的key值
        if (k == key) //若k与参数key相同,则返回 e
            return e;
        if (k == null) //若k为null
            //清理数组中下标下的数据​
            expungeStaleEntry(i);
        else
            //下一个元素​
            i = nextIndex(i, len);
        //e为下一个索引下标下的Entry数据​
        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(); //清理e的数据
            expungeStaleEntry(i); //去除数组中的索引节点
            return;
        }
    }
}
​
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    // 由于Entry中value引用类型为强引用类型,为防御内存泄露 
    //​所以我们需要先把value置null 然后再将数组中的整个节点置null​
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    
     ​size--; 条目数减 1
    // Rehash until we encounter null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) { //遍历tab数组
        ThreadLocal<?> k = e.get();
        //遍历过程中若发现其他元素的key为null 则进行清除​
        if (k == null) { 
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            //若当​元素key的索引下标 不等于 i
            if (h != i) {
                tab[i] = null; 
                // Unlike Knuth 6.4 Algorithm R, we must scan until
                // null because multiple entries could have been stale.
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e; //将e放至tab数组的h索引下,是为了更新旧条目数据存储
            }
        }
    }
    return i;
}​​

问题探究

  • [1] 我通过分析源码发现一个问题,在Entry类中由于key的引用类型为弱引用而value的引用类型为强引用,当key被GC掉key的值就为null,在set/get/remove函数中都有对key的判空,并在清除数组节点时先将value置null,这所做的一切都是为了防止内存泄露!但是这只是减小了发生内存泄露的概率,并没有解决问题;
    若有一个场景:我们在线程池中获取一个线程构建Looper,当Looper退出循环,此时由于线程来自于线程池中无法被回收,当线程被再次使用,value依然可以被获取因此会发生内存泄露.若要避免需要我们在使用完手动将调用ThreadLocal.reomve 即 ThreadLocalMap.remove函数 将value置成null。
  • [2] 我们在创建ThreadLocalMap时传入的key并不是Thread对象而是ThreadLocal本身,那么又是如何实现与Thread进行关联和唯一指定的呢?原来ThreadLocal被内在Thread的成员变量中,通过Thread.threadLocal获取唯一成员变量作为ThreadLocalMap的key作为唯一指定.

This ALL! Thanks EveryBody!

相关文章

  • ThreadLocal精解

    基础定义 [1] ThreadLocal提供了线程本地的实例.[2] 同一个变量在每一个线程都会创建一个相对独立的...

  • ThreadLocal

    ThreadLocal辨析 与Synchonized的比较 ThreadLocal和Synchonized都用于解...

  • 当ThreadLocal碰上线程池

    ThreadLocal使用 ThreadLocal可以让线程拥有本地变量,在web环境中,为了方便代码解耦,我们通...

  • 多线程知识梳理(9) - ThreadLocal

    一、基本概念 1.1 ThreadLocal 的用途 首先,我们来看一下JDK源码中对于ThreadLocal的解...

  • ThreadLocal使用全解

    一、何为ThreadLocal 1、ThreadLocal的含义 ThreadLocal,即线程变量,是一个以Th...

  • ThreadLocal解决线程安全

    线程不安全示例 SimpleDateFormat 是不安全的,被多个线程共用情况下。 使用ThreadLocal解...

  • ThreadLocal

    ThreadLocal 简介ThreadLocal 使用ThreadLocal 原理InheritableThre...

  • ThreadLocal

    ThreadLocal 由于 ThreadLocal 支持范型,如 ThreadLocal< StringBuil...

  • 精通Java并发 - ThreadLocal

    3. ThreadLocal[#3-threadlocal]3.1 ThreadLocal 常用方法[#31-th...

  • ThreadLocal 详解

    目录概况: ThreadLocal 的定义 ThreadLocal 的用途 ThreadLocal的设计细节 Th...

网友评论

      本文标题:ThreadLocal精解

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