ThreadLocal源码分析

3 篇文章 1 订阅
3 篇文章 0 订阅

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

 

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

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


    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;
    }

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }


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

        /**
         * 
         * 存放数据的table,
         */
        private Entry[] table;

        /**
         * 数组里entrys的个数
         */
        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);
        if (map != null)
            map.set(this, value);
        else
            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),
         * 那么进行rehash()
         */
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

 

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

      ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            //初始化table
            table = new Entry[INITIAL_CAPACITY];
            //计算哈希索引
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
           //设置初始值
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            //设置阀值
            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方法源码

       public T 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() {
        return null;
    }

    private T setInitialValue() {
          //获取初始化的value,默认为null
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
        // 不为空就set值
            map.set(this, value);
        else
              //为空第一次初始化,体现了延迟加载的策略
            createMap(t, value);
        return 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;
            else
                  //如果找不到对应的值就调用此方法
                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);
                else
                    //通过线性探测法向后扫描
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

总结

  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;
                }
            }
        }

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员蛋蛋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值