ThreadLocal 源码分析-扩容和get方法

192 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情

get方法

ThreadLocal中的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();
    }

Thread t = Thread.currentThread(); get获取之前,从获取当前的线程对象,因为ThreadLocal保存的数据也都是属于当前线程的

getMap(t); 从当前线程中获取ThreadLocalMap对象

ThreadLocalMap.Entry e = map.getEntry(this); 从ThreadLocalMap中获取entry,entry中保存的就是具体的值

在ThreadLocalMap的getEntry方法中,方法内部首先是将参数的hash值与数组大小取模并减一,作为索引位置查询数据,如果找不到对应的数据(数据为空)则自选将索引+1继续寻找,直到找到为止。因为这种不断的自旋找法,如果使用了大量的key的话,数据获取的效率就会很低。

扩容方法

        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 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
                    } 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;
        }

Entry[] oldTab = table; 先拿出旧的数组

int newLen = oldLen * 2; 扩容的新数组为旧数组大小的2倍

for (int j = 0; j < oldLen; ++j) { ... } 将老数组的值拷贝到新数组中

setThreshold(newLen); 设置数组新的扩容阈值,阈值为数组长度的三分之二

这里的扩容不像ArrayList或者其他线程安全的集合扩容方法,这里没有考虑modCount,也就是没有考虑到线程不安全的情况,这是因为本来这就是线程的独属的方法,只有一个线程,也就是线程本身才能操作这部分的数据,所以不会存在线程不安全的情况。

总结

ThreadLocals.ThreadLocalMap 和 InheritableThreadLocals.ThreadLocalMap 是线程独有的属性,每个线程的ThreadLocal都是隔离的,所以是线程安全的。

当在一个线程中创建另一个线程时,也就存在父子线程的关系,但是子线程的ThreadLocal不会和父线程的ThreadLocal共享。