ThreadLocal源码分析

属性字段

java 复制代码
//哈希码
private final int threadLocalHashCode = nextHashCode();

//下一个线程的哈希码
private static AtomicInteger nextHashCode =
    new AtomicInteger();

//增量值
private static final int HASH_INCREMENT = 0x61c88647;

ThreadLocal使用开放地址法伪随机探测的解决方式,通过魔数0x61c88647(黄金分割数*2^32)作为步长优化哈希分布,来解决哈希冲突

内部类

Entry

Entry是ThreadLocal的存储单元,实际存储数据是一个个Entry

scala 复制代码
static class Entry extends WeakReference<ThreadLocal<?>> {
    //值
    Object value;
    
    Entry(ThreadLocal<?> k, Object v) {
        //key 弱引用
        super(k);
        value = v;
    }
}

Entry继承弱引用(WeakReference),当发生垃圾回收的时候回收key,防止key一直被引用造成大量的key堆积形成内存溢出;当key被垃圾回收时,value还是存在内存中的,大量的value堆积也会造成内存溢出,正确的做法是使用完了显示释放(remove())。

ThreadLocalMap

1. 属性字段

arduino 复制代码
//初始容量
private static final int INITIAL_CAPACITY = 16;

//数据
private Entry[] table;

//大小
private int size = 0;

//阈值
private int threshold; // Default to 0

2.构造方法

构建一个初始,包括第一个key和第一个value

scss 复制代码
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    //初始数组
    table = new Entry[INITIAL_CAPACITY];
    //定位槽位
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    //赋值
    table[i] = new Entry(firstKey, firstValue);
    //初始size为1
    size = 1;
    //设置阈值为容量的2/3
    setThreshold(INITIAL_CAPACITY);
}

私有构造方法,线程在初始化的时候通过ThreadLocal.createInheritedMap()调用,把父线程的inheritableThreadLocals变量拷贝到子线程的inheritableThreadLocals的变量中,这样子线程可以可见父线程的数据,InheritableThreadLocal实现的关键点

ini 复制代码
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
                Entry c = new Entry(key, value);
                //计算槽位
                int h = key.threadLocalHashCode & (len - 1);
                //哈希冲突,循环找空位置
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

核心方法

set(T value)

scss 复制代码
public void set(T value) {
    //当前线程
    Thread t = Thread.currentThread();
    //获取ThreadLocalMap,保存在线程对象里面
    //线程首次进来是空的
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}

向ThreadLocal里插入值

ini 复制代码
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();
        //key相同,覆盖value
        if (k == key) {
            e.value = value;
            return;
        }
        //为空,k被垃圾回收了,因为k是弱引用
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //空槽位放入
    tab[i] = new Entry(key, value);
    //size加一
    int sz = ++size;
    //不存在无效Entry,并且大小大于等于阈值进行
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

下个槽位

arduino 复制代码
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

替换无效槽位

ini 复制代码
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                               int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    Entry e;
    int slotToExpunge = staleSlot;
    //从当前索引向上找,找到第一个key为空的槽位停止,更新slotToExpunge
    for (
    //上一个槽位
    int i = prevIndex(staleSlot, len);
          //
         (e = tab[i]) != null;
         i = prevIndex(i, len))
         //key是空的
        if (e.get() == null)
            slotToExpunge = i;
    for (
    //下一个槽位
    int i = nextIndex(staleSlot, len);
          //不为空说明存在哈希冲突
         (e = tab[i]) != null;
         //继续看下一个槽位
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        //key相同
        if (k == key) {
            //覆盖value
            e.value = value;
            //交换i和staleSlot,把set方法哈希冲突key为空的槽位和从此槽位向下找存在哈希冲突key值             //相等的槽位交换
            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;

            //相等说明,向上找没找到key为空的槽位
            if (slotToExpunge == staleSlot)
                //更新为向下找key相等的槽位
                slotToExpunge = i;
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }
        //k被回收,向上找没找到key为空的槽位
        if (k == null && slotToExpunge == staleSlot)
            //更新为i
            slotToExpunge = i;
    }
    //在上面循环中没找到key,构造新的Entry放入即可
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);

    // 不相等说明还有存在要清除的Entry,清除
    if (slotToExpunge != staleSlot)
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

清除无效槽位

ini 复制代码
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // 清除value
    tab[staleSlot].value = null;
    //清除Entry
    tab[staleSlot] = null;
    //长度减一
    size--;

    // Rehash until we encounter null
    Entry e;
    int i;
    //向后找,循环中的都是存在哈希冲突的
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        //key已被垃圾回收
        if (k == null) {
            //清除value
            e.value = null;
            //清除Entry
            tab[i] = null;
            //长度减一
            size--;
        } 
        //冲突的槽位重新分配槽位
        else {
            //重新计算槽位
            int h = k.threadLocalHashCode & (len - 1);
            //不是当前槽位(理想槽位)
            if (h != i) {
                //清空当前Entry
                tab[i] = null;
                //向后找哈希不冲突的位置
                while (tab[h] != null)
                    h = nextIndex(h, len);
                //复制    
                tab[h] = e;
            }
        }
    }
    return i;
}

清除槽位

ini 复制代码
private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
        i = nextIndex(i, len);
        Entry e = tab[i];
        //无效Entry
        if (e != null && e.get() == null) {
            n = len;
            removed = true;
            //清除无效Entry返回有效的槽位
            i = expungeStaleEntry(i);
        }
    } while ( (n >>>= 1) != 0);
    return removed;
}

获取ThreadLocalMap

javascript 复制代码
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

创建ThreadLocalMap

javascript 复制代码
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

重新哈希

scss 复制代码
private void rehash() {
    //清除所有无效Entry
    expungeStaleEntries();

    //size>=阈值-阈值/4
    if (size >= threshold - threshold / 4)
        //扩容
        resize();
}

扩容

ini 复制代码
private void resize() {
    //原数组
    Entry[] oldTab = table;
    //原长度
    int oldLen = oldTab.length;
    //新长度为原长度的2呗
    int newLen = oldLen * 2;
    //构造新的Entry数组
    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();
            //无效Entry
            if (k == null) {
                //清除value
                e.value = null; // Help the GC
            } else {
                //重新计算槽位
                int h = k.threadLocalHashCode & (newLen - 1);
                //循环直到找到哈希不冲突的槽位
                while (newTab[h] != null)
                    h = nextIndex(h, newLen);
                //放入新数组    
                newTab[h] = e;
                count++;
            }
        }
    }
    //设置阈值
    setThreshold(newLen);
    //设置size
    size = count;
    //更新原数组
    table = newTab;
}

set方法逻辑总结:

  1. 获取当前线程的ThreadLocalMap对象;
  2. 为空。创建ThreadLocalMap对象,初始Entry table默认容量为16,计算槽位构建Entry放入数组中,设置阈值为初始容量的2/3;
  3. 不为空。先计算槽位,如果有哈希冲突,判断是否是同一个ThreadLocal对象(key是否相同)。相同的话更新value值,结束;key为空,替换无效的Entry,结束;
  4. 没有哈希冲突放入值即可;
  5. 清除无效槽位,判断是否需要扩容,需要的话进行扩容。

在set方法中会清除无效的Entry,整理Entry数组,扩容的时候也会清除无效的Entry,从而保证Entry数组的整洁,减小垃圾残留。

get()

java 复制代码
public T get() {
    //当前线程
    Thread t = Thread.currentThread();
    //当前线程的ThreadLocalMap对象
    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;
        }
    }
    //初始化value
    return setInitialValue();
}
csharp 复制代码
private Entry getEntry(ThreadLocal<?> key) {
    //计算槽位
    int i = key.threadLocalHashCode & (table.length - 1);
    //结果
    Entry e = table[i];
    //不为空,key相等返回结果
    if (e != null && e.get() == key)
        return e;
    else
        //向后找key相等的Entry
        return getEntryAfterMiss(key, i, e);
}
ini 复制代码
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    //循环向后找直到key相等,返回结果
    while (e != null) {
        ThreadLocal<?> k = e.get();
        //key相等返回
        if (k == key)
            return e;
        //k为空清除无效的Entry    
        if (k == null)
            expungeStaleEntry(i);
        else
            //下一个槽位
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}
scss 复制代码
private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    //下面的逻辑和set方法一样
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
    //是TerminatingThreadLocal类型,注册。
    //用于自定义清理资源
    if (this instanceof TerminatingThreadLocal) {
        TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
    }
    //返回初始化的值
    return value;
}
csharp 复制代码
//子类可扩展,默认返回空
protected T initialValue() {
    return null;
}

get方法逻辑总结:

  1. 获取当前线程的ThreadLocalMap对象;
  2. 不为空。计算哈希值,获取槽位,根据槽位获取Entry。Entry不为空并且key相等,返回value值,结束;Entry为空向后查找不为空的Entry(空的Entry清除掉),并且key相等返回value,结束;
  3. 为空。走初始化value逻辑。执行initialValue() ,把初始化值设置进ThreadLocal中,返回结果。
相关推荐
HelloZheQ1 小时前
Go:简洁高效,构建现代应用的利器
开发语言·后端·golang
caihuayuan51 小时前
[数据库之十四] 数据库索引之位图索引
java·大数据·spring boot·后端·课程设计
风象南2 小时前
Redis中6种缓存更新策略
redis·后端
程序员Bears3 小时前
Django进阶:用户认证、REST API与Celery异步任务全解析
后端·python·django
非晓为骁3 小时前
【Go】优化文件下载处理:从多级复制到零拷贝流式处理
开发语言·后端·性能优化·golang·零拷贝
北极象3 小时前
Golang中集合相关的库
开发语言·后端·golang
喵手3 小时前
Spring Boot 中的事务管理是如何工作的?
数据库·spring boot·后端
玄武后端技术栈5 小时前
什么是延迟队列?RabbitMQ 如何实现延迟队列?
分布式·后端·rabbitmq
液态不合群6 小时前
rust程序静态编译的两种方法总结
开发语言·后端·rust
bingbingyihao7 小时前
SpringBoot教程(vuepress版)
java·spring boot·后端