ThreadLocal源码分析

145 阅读6分钟

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战


ThreadLocal类提供了线程局部 (thread-local) 变量。这些变量与普通变量不同,每个线程都可以通过其 get 或 set方法来访问自己的独立初始化的变量副本。ThreadLocal 实例是private static 类型,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。-------ThreadLocal类注释

首先看一下ThreadLocal的部分核心源码

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

    private T setInitialValue() {

        T value = initialValue();

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

            map.set(this, value);

            createMap(t, value);

    public void set(T value) {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

            map.set(this, value);

            createMap(t, value);

     public void remove() {

         ThreadLocalMap m = getMap(Thread.currentThread());

             m.remove(this);

    ThreadLocalMap getMap(Thread t) {

        return t.threadLocals;

从ThreadLocal几个重要的方法源码可以看出来,set,get,remove操作都离不开ThreadLocalMap,ThreadLocalMap是ThreadLocal的静态内部类,是实现ThreadLocal功能的核心,这个类本质上是一个map,和HashMap之类的实现相似,依然是key-value的形式,其中有一个内部类Entry,其中key可以看作是ThreadLocal实例,但是其本质是持有ThreadLocal实例的弱引用。

ThreadLocalMap

ThreadLocalMap是ThreadLocal的静态内部类,其承载了ThreadLocal功能的核心实现,理解透ThreadLocalMap就对整个ThreadLocal也就基本理解了。

基本属性及Entry内部类

/**

*/

  * Entry继承WeakReference,并且用ThreadLocal作为key.如果key为null * (entry.get() == null)表示key不再被引用,表示ThreadLocal对象被回收 * 因此这时候entry也可以从table从清除。static class Entry extends WeakReference<ThreadLocal<?>> {            /** The value associated with this ThreadLocal. */            Object value;            Entry(ThreadLocal<?> k, Object v) {                super(k);                value = v;         * 初始容量,必须是2的n次方,同样数组长度必须是2的n次方        private static final int INITIAL_CAPACITY = 16;        private Entry[] table;        private int size = 0;        private int threshold; // Default to 0         *阀值的具体指的设置,定义长度的2/3,超过这个阀值就会进行扩容
        private void setThreshold(int len) {  threshold = len * 2 / 3;}

set 方法

首先通过ThreadLocal的set方法来了解TheadLocalMap的存值机制。

    public void set(T value) {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

            map.set(this, value);

            createMap(t, value);

先是获取当前的线程,然后通过getMap方法获取ThreadLocalMap,这个getMap具体做了哪些事呢,我们接着往下看源码

ThreadLocalMapgetMap(Thread t) {
returnt.threadLocals;
}

通过获取Thread的成员属性threadLocals来拿到这个ThreadLocalMap,所以ThreadLocal没有ThreadLocalMap的引用,是在Thread中

   ThreadLocal.ThreadLocalMap threadLocals = null;

在Thread类中源码如上,这样就很好理解了,getMap方法传入当前类,然后拿到当前线程的ThreadLocalMap,接着源码继续,判断获取的map是否有值,如果有值,调用map的set方法进行设置,然后大家看到,真正设置值的是ThreadLocalMap的set方法。

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

            Entry[] tab = table;

            int len = tab.length;

            int i = key.threadLocalHashCode & (len-1);

         * 如果当前索引上的table[i]不为空,在没有return的情况下,

         * 就使用nextIndex()获取下一个(线性探测法)。

            for (Entry e = tab[i];

                 e != null;

                 e = tab[i = nextIndex(i, len)]) {

                ThreadLocal<?> k = e.get();

                if (k == key) {

                    e.value = value;

                    return;

             * table[i]上的key为空,说明被回收了。

             * 这个时候说明该table[i]可以重新使用,

             * 用新的key-value将其替换,并删除其他无效的entry

                if (k == null) {

                    replaceStaleEntry(key, value, i);

                    return;

 //找到为空的插入位置,插入值,在为空的位置插入需要对size进行加1操作

            tab[i] = new Entry(key, value);

            int sz = ++size;

         * cleanSomeSlots用于清除那些e.get()==null,

         * 也就是table[index] != null && table[index].get()==null

         * 之前提到过,这种数据key关联的对象已经被回收,所以这个* * * 

         *  Entry(table[index])可以被置null。

         * 如果没有清除任何entry,并且当前使用量达到了负载因子所定义(长度的2/3),

            if (!cleanSomeSlots(i, sz) && sz >= threshold)

                rehash();

首先,要计算索引,这个索引怎么计算呢,要先看下ThreadLocalMap的构造函数

      ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {

            table = new Entry[INITIAL_CAPACITY];

            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

            table[i] = new Entry(firstKey, firstValue);

            setThreshold(INITIAL_CAPACITY);

firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1),& (INITIAL_CAPACITY - 1),这是取模的一种方式,对于2的n次方作为模数取模,用此代替%(2^n),这也就是为啥容量必须为2的n次方。

    private final int threadLocalHashCode = nextHashCode();

    private static AtomicInteger nextHashCode =

        new AtomicInteger();

    private static final int HASH_INCREMENT = 0x61c88647;

    private static int nextHashCode() {

        return nextHashCode.getAndAdd(HASH_INCREMENT);

定义了一个AtomicInteger类型,每次获取当前值并加上HASH_INCREMENT ,HASH_INCREMENT = 0x61c88647,关于这个值和斐波那契数列有关,其主要目的就是为了让哈希码能均匀的分布在2的n次方的数组里, 也就是Entry[] table中,如果发生哈希冲突是如何解决呢,ThreadLocalMap使用线性弹测法来解决哈希冲突,线性探测法的地址增量di = 1, 2, ... , m-1,其中,i为探测次数。该方法一次探测下一个地址,直到有空的地址后插入,若整个空间都找不到空余的地址,则产生溢出。假设当前table长度为16,也就是说如果计算出来key的hash值为14,如果table[14]上已经有值,并且其key与当前key不一致,那么就发生了hash冲突,这个时候将14加1得到15,取table[15]进行判断,这个时候如果还是冲突会回到0,取table[0],以此类推,直到可以插入。

来看下线性探测的相关代码

       private static int nextIndex(int i, int len) {

            return ((i + 1 < len) ? i + 1 : 0);

        private static int prevIndex(int i, int len) {

            return ((i - 1 >= 0) ? i - 1 : len - 1);

这个 set方法就大致讲解完了,比较长,我们来总结一下

  1. 根据哈希码和数组长度求元素放置的位置

  2. 从第一步得出的下标开始往后遍历,如果key相等,就覆盖value,如果key为null,用新的key、value覆盖,同时清理历史key=null的陈旧数据。

  3. 如果超过阀值,就需要再哈希。

get方法

首先看一下ThreadLocal的get方法源码

         //同set方法一样,先获取当前线程

        Thread t = Thread.currentThread();

          //获取当前ThreadLocalMap实例

        ThreadLocalMap map = getMap(t);

        if (map != null) {

            ThreadLocalMap.Entry e = map.getEntry(this);

            if (e != null) {

                //不为空就返回value

                @SuppressWarnings("unchecked")

                T result = (T)e.value;

                return result;

        return setInitialValue();

     protected T initialValue() {

    private T setInitialValue() {

          //获取初始化的value,默认为null

        T value = initialValue();

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

            map.set(this, value);

              //为空第一次初始化,体现了延迟加载的策略

            createMap(t, value);

具体解释以及在注释中标注,主要看下ThreadLocalMap的getEntry方法

       private Entry getEntry(ThreadLocal<?> key) {

          //通过key获取具体的哈希值,既而拿到具体的Entry

            int i = key.threadLocalHashCode & (table.length - 1);

            Entry e = table[i];

            if (e != null && e.get() == key)

                return e;

                  //如果找不到对应的值就调用此方法

                return getEntryAfterMiss(key, i, e);

        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)

                      //清除无效的entry

                    expungeStaleEntry(i);

                    //通过线性探测法向后扫描

                    i = nextIndex(i, len);

                e = tab[i];

总结

  1. 从当前线程中获取ThreadLocalMap,查询·当前ThreadLocalMap变量实例对应的Entry,如果不为null,获取value,返回

  2. 如果map为null,也就是坏没有初始化,走初始化方法。

remove方法

同样的,先看下ThreadLocal的remove方法源码

public voidremove() {
ThreadLocalMap m = getMap(Thread.currentThread());
   if(m !=null)
m.remove(this);
}

可以看到就是通过getMap获取当前ThreadLocalMap实例,然后调用ThreadLocalMap中的remove方法。

       private void remove(ThreadLocal<?> key) {

            Entry[] tab = table;

            int len = tab.length;

           //通过key获得具体的索引

            int i = key.threadLocalHashCode & (len-1);

               //进行for循环,通过线性探测查找正确的key

            for (Entry e = tab[i];

                 e != null;

                 e = tab[i = nextIndex(i, len)]) {

                if (e.get() == key) {

                //调用weakrefrence的clear()清除引用

                    e.clear();

                  // 连续段清除

                    expungeStaleEntry(i);

                    return;

卧龙小蛋: 做一个不安分的程序员,关注卧龙小蛋,分享技术进阶成长之路,每天进步一点点