源码分析ThreadLocal数据结构及原理

开篇摘要

最近被问及threadlocal的数据结构,一时不知如何描述。虽然平时也用过threadlocal来做变量的线程隔离,然而却没有实际去研究过threadlocal的数据结构。实在惭愧,今天好好看了一下threadlocal的源码,然后写一点心得。

开篇先说明一点,网上很多说法说threadlocal维护的map里面,key用的是线程id,这种说法是不正确的,下面会说明为什么不正确。希望看到帖子的小伙伴也能一起思考下,我的见解是否正确。

源码分析

先上一个示例代码:


public class threadLocalTest {

    static class MyThread extends Thread {
    	// 声明两个threadlocal变量
        private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
        private static ThreadLocal<String> threadLocal2 = new ThreadLocal<>();

        @Override
        public void run() {
            super.run();
            // thread local赋值及取值
            threadLocal.set(1);
            threadLocal2.set("this is a threadLocal param");
            System.out.println(getName() + " threadLocal.get() = " + threadLocal.get());
            System.out.println(getName() + " threadLocal2.get() = " + threadLocal2.get());
        }
    }
    
    public static void main(String[] args) {
    	// 创建线程
        MyThread myThread = new MyThread();
        myThread.setName("ThreadA");
        // 启动线程
        myThread.start();
    }
}

结果显而易见,两个变量被打印出来。那么threadlocal的数据结构是怎样的,set和get方法又做了哪些事情呢?

thread 数据接口


public class Thread implements Runnable {
	...
	
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    /* 与此线程有关的ThreadLocal值。 该map由ThreadLocal类维护。 */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    /* 与此线程有关的InheritableThreadLocal值。 该map由ThreadLocal类维护。 */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
	...
}

在thread类中,声明类两个threadlocal相关的变量,这两个变量就是用来保存和当前线程相关的thread local变量的。thread线程本身不维护这两个变量,而是交由thread local类来维护。

threadlocal数据结构


public class ThreadLocal<T> {

    /**
     * 根据每个线程生成的一个线性hash值
     * 这个hash值会被记录到ThreadLocals(即Thread.threadLocals 和
     * inheritableThreadLocals)
     * ThreadLocal作为keys,实际是通过threadLocalHashCode来搜索。
     * 这是一个自定义的hashcode,只在ThreadLocalMaps中有效。
     * 这个hash值能很好的保证同一个线程中的不同threadlocal变量间不产生冲突
     */
    private final int threadLocalHashCode = nextHashCode();

    /**
     * 产生下一个给定的hash值。
     * 采用AtomicInteger数据结构,保证每次更新的原子性
     * 从0开始
     */
    private static AtomicInteger nextHashCode =
            new AtomicInteger();

    /**
     * 生成hash值之间的步长
     * 通过这个步长,可以将thread-local IDs转换成可以均匀散列在一个2的指数级大小
     * 的table中的hash值
     */
    private static final int HASH_INCREMENT = 0x61c88647;

    /**
     * 返回下一过hash值
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }


    /**
     * 构造一个thread local 变量
     * 初始化的值是通过supplier上的get方法来决定的
     *
     * @param <S> thread local变量的类型
     * @param supplier 初始化变量的提供者
     * @return 返回一个新的thread local变量
     * @throws NullPointerException 如果supplier为null
     * @since 1.8
     */
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new ThreadLocal.SuppliedThreadLocal<>(supplier);
    }

    /**
     * 无参构造方法
     */
    public ThreadLocal() {
    }

    /**
     * 获取当前线程的当前thread local变量的值的拷贝
     *
     * 如果当前线程没有当前thread local变量的值,那么会调用initialValue方法来
     * 初始化变量,并返回初始化后的值
     *
     * @return 当前线程的当前thread-local变量的值
     */
    public T get() {
        Thread t = Thread.currentThread();
        java.lang.ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null) {
            java.lang.ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    /**
     * set方法的变体,用来设置初始值
     *
     * 当用户重写来set方法时,使用该方法
     * @return 初始值
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        java.lang.ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    /**
     * 设置当前线程的当前thread local变量的值
     * 大部分的子类没有必要重写这个方法,仅依靠initialValue方法来这是thread locals的值
     *
     * @param value 需要保存到threadlocal变量中到值
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        java.lang.ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    /**
     * 删除当前线程到当前threadlocal变量
     */
    public void remove() {
        ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null)
            m.remove(this);
    }

    /**
     * 获取指定线程的ThreadLocalMap
     * 这个方法在InheritableThreadLocal中被重写
     *
     * @param t 当前线程
     * @return 返回map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     *
     * 创建一个和指定线程相关联的ThreadLocalMap,并给定第一个值
     * 
     * @param t 当前线程
     * @param firstValue 初始化entry的值
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    /**
     * 创建inherited thread locals map的工厂方法
     * 被设计为只能在线程的构造方法中被调用
     *
     * @param  parentMap 关联的父线程
     * @return 一个包含父map中变量的map
     */
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

}

上述代码介绍了ThreadLocal类中的主要数据结构。相关的说明已经在注释中给出,可以参照源码中的英文注释。除了以上的数据结构外,在ThreadLocal中构建了上面一直在用到的ThreadLocalMap。这一块比较复杂,所以分开来描述。
参见代码:

 /**
     * ThreadLocalMap是一个自定义的hash map,只用来维护thread local值
     * 任何操作都不能在ThreadLocal类之外被引用。这个类是包内私有都,以允许在Thread类中
     * 声明字段。该hash map使用WeakReferences作为key值,以帮助处理那些大并且生命周期
     * 较长的引用。然而,由于不是使用参考队列(reference queues),因此尽在table空间
     * 不足的时候才会淘汰果实的entry。
     */
    static class ThreadLocalMap {

        /**
         * 这个map的内容条目扩展了WeakReference,并使用其主要的字段作为key
         * 这个key其实对应着threadlocal对象
         *
         * 值得注意的是,如果key值为null(例如entry.get() == null)意味着这个key
         * 已经不再被引用,且这个条目可以从table中清除。这些条目在以下代码中称为过时
         * 条目(stale entries)
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /**
             * 和这个ThreadLocal关联的value值
             */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * 初始容量,必须是2的指数倍
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        /**
         * 用来存储数据的数组。是一个可扩展的数据
         * 数组的长度必须为2的指数倍
         */
        private Entry[] table;

        /**
         * table中的条目数量
         */
        private int size = 0;

        /**
         * 触发扩展(resize)的最大容量,默认为0
         */
        private int threshold; // Default to 0

        /**
         * 设置threshold,默认为长度的2/3,向下取整
         * 即负载系数为2/3
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * 递增对len取模后的值i
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * 递减对len取模后的值i
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }


        /**
         * 带参数对构造方法
         *
         * @param firstKey
         * @param firstValue
         */
        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;
            setThreshold(INITIAL_CAPACITY);
        }

        /**
         * 构造ThreadLocalMap,通过传入一个父map
         * 新map中包含父map中的所有条目
         *
         * @param parentMap 父线程关联的map
         */
        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++;
                    }
                }
            }
        }

        /**
         * 获取key关联的entry,这个方法本身只能处理快速路径:直接命中存在的key
         * (未命中的时候,调用getEntryAfterMiss方法,查找没有命中key的条目)
         * 使用快速路径,可以提高查询性能,且易于操作
         * @param key
         * @return
         */
        private Entry getEntry(ThreadLocal<?> key) {
            // 通过hashcode和table长度计算存储位置
            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);
        }

        /**
         * 当直接计算的位置上没有找到对应的key值,则使用这个方法继续查询
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        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;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

        /**
         * 设置和key关联的value
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) {

            // 在这里没有项get方法一样使用快速路径。原因是通过set方法新建一个条目
            // 和通过set方法替换一个条目一样普遍。在这种情况下,快速路径失败的可能
            // 性更高

            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();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

        /**
         * 按key删除条目entry
         */
        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;
                }
            }
        }
    }

上述代码说明了ThreadLocalMap的主要的数据结构和方法。如注释所说,这个map是一个自定义的hashMap,其key并不是直接维护在map中,而是维护在Entry扩展的WeakReference中。在map中维护了一个tables数组。与其说,ThreadLocalMap是一个map,倒不如说,是一个map组成的数组。ThreadLocalMap是线程里threadLoclas变量的数据结构,一个线程维护一个ThreadLocalMap供线程共享,ThreadLocalMap中维护了一个数组tables,这个tables中存储的是Entry,简单的说,Entry就是一个保存了以ThreadLocal变量的hashCode作为key,ThreadLocal的实例作为value的健值对。
这基本上就是ThreadLocal的数据结构了。下面通过对上述的示例进行断点调试,一起来看看运行的过程中,数据怎么保存在ThreadLocal中的。

断点调试

首先,创建TreadLocal变量,创建时,会调用初始化方法,初始化hashCode。
在这里插入图片描述
然后调用set方法为ThreadLocal设置一个初始值(value为null)。
在这里插入图片描述
设置完两个变量后,在get方法中,我们可以看到在当前线程中被设置好的ThreadLocal变量threadLocals
在这里插入图片描述
threadLocals的tables中,包含一个位于第1位上的entry,其hashCode和value值就是上述设置的值。还有一个位于第10位上的entry。在get函数的getMap方法中,获取的就是t.threadLocals。
在这里插入图片描述
在getEntry方法中,就是通过this的hashCode去匹配tables中的hashCode,然后获取到对应的Entry。
在这里插入图片描述
然后返回entry中的value值。

回顾开篇

回到开篇说的问题,ThreadLocalMap中的key,是线程ID吗?答案是否定的。理由有以下几点:
1,ThreadLocal是存放在线程对象里的,是一个线程隔离的变量,而对于在同一个线程内的ThreadLocal使用线程ID作为key,区分不了变量。因为一个线程内,线程id是相同的。
2,通过查看源码,我们可以知道,ThreadLocalMap的key,其实是ThreadLocal变量的hash值。在同一个线程中,每一个ThreadLocal变量的hashCode是不同的,可以起到区分ThreadLocal的目的。
3,ThreadLocalHashCode还有一个用途,就是计算ThreadLocal变量在tables中的位置。方法是
i = this.threadLocalHashCode & (tables.len - 1)
计算出来的i就是这个threadLocal在tables中的位置。需要注意的是,如果这个位置已经有Entry占用里,set方法会继续寻找下一个位置
重新看一下set方法

private void set(ThreadLocal<?> key, Object value) {

	// 获取相关变量
    Entry[] tab = table;
    int len = tab.length;
    // 计算当前threadLocal变量的位置
    int i = key.threadLocalHashCode & (len-1);

	// 如果当前位置不为null
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
         // 获取当前位置的threadLocal值
        ThreadLocal<?> k = e.get();
        // 如果存放的就是我要设置的ThreadLocal,则更新当前值
        if (k == key) {
            e.value = value;
            return;
        }
        // 如果当前的值已经变为null,说明已经过时了
        if (k == null) {
        	// 用要设置的ThreadLocal去替换当前位置上
        	// 已经过时的ThreadLocal
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    // for循环中都不满足,且通过nextIndex方法找到下一个为null的位置
    // 然后插入Entry
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // 标记过时的entry,并且判断tables大小是否已经达到了负载因子。
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
    	// 如果达到了,则进行rehash
        rehash();
}

下面是rehash的代码

private void rehash() {
	// 先清除过时的entries
    expungeStaleEntries();

    // size是否大于负载因子的3/4
    if (size >= threshold - threshold / 4)
    	// 如果大于,则扩展tablse
        resize();
}

这里没有直接使用负载因子,而是使用负载因子的3/4来判定,是为了降低程序出错的几率。在resize之前,如果大量的 threadLocal插入,导致tables满载。通过set方法的代码可以看到,tables满载的情况下,代码会进入不断寻找为null的Entry的死循环。所以采用3/4来提前触发resize
这一部分的设计,和GC收集器中的CMS收集器的负载因子设计类似。CMS收集器如果负载因子设置不合理,也会抛出异常。
这是我通过读代码得出的结论。如果有我看漏的地方来控制这块逻辑,还请大佬们指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值