ThreadLocal源码解读

本文深入解析了ThreadLocal的工作原理,包括其内部结构ThreadLocalMap的设计、Entry对象的作用、以及如何通过哈希算法高效地存储和检索线程局部变量。同时,文章还探讨了ThreadLocal在多线程环境下如何避免内存泄漏等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ThreadLocal的三个理论基础

1. 每个线程都有一个自己的ThreadLocal.ThreadLocalMap对象,ThreadLocal类中定义了静态类ThreadLocalMap,

静态类ThreadLocalMap中定义了Entry结构存储

static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

2. 每个ThreadLocal对象都有一个循环计数器

3. ThreadLocal.get()取值,就是根据当前的线程,获取线程中自己的ThreadLocal.ThreadLocalMap,然后在这个Map中根据根据第二点中循环计数器取得一个特定value值

两个数学问题

1. ThreadLocal.ThreadLocalMap规定了table的大小必须是2的N次幂

/**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

计算机处理位运算的效率比数学运算要高,例如ThreadLocalMap中获取Entry对象的方法

 private Entry getEntry(ThreadLocal key) {
            int i = key.threadLocalHashCode & (table.length - 1);//1
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

上面第一行代码,通过与运算获取到Entry在数组中的索引

如果使用%的话,table.length=16,存在一个数字23,23%16=7,如果转为上面的二进制运算的话:

23                 -> 00010111 

&

table-1 = 15 -> 00001111

result: 00000111 就是十进制的 7 ,效率更高

2. 对于上面取模获取在table中索引位置时候,threadLocalhashCode源码如下:

private final int threadLocalHashCode = nextHashCode();
 private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
 private static final int HASH_INCREMENT = 0x61c88647;

length 为 16 和 32 时候,取模生成的值


通过取模方式获取索引的时候,每次都会在原来的threadLocalHashCode的基础上加上0x61c88647,这样的结果是生成的hash值分散,而且在length扩容为,2的n次幂,之后,生成的hash值会和前面扩容前的值一致,这就保证了threadLocalHashCode可以从任何地方开始。

set(T 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);
    }
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

1. 获取当前线程

2. 获取当前线程的ThreadLocalMap,不为null,就往里面设值

3. 否则,就去创建ThreadLocalMap,并设值

看下,第三步的源码:

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, 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);
        }

创建一个Entry数组,将初始值放入,取模运算后的索引位置,并且置entry数量为1,而且从ThreadLocalMap中看出并没有next节点,也就是ThreadLocalMap不是类似于hashmap的链表结构,而是开地址法,每次递增一个值,取模运算计算索引存放元素。这样的结果就是设置同一个value放到table中的位置会不一样的

接着,看下,ThreadLocalMap的设值源码:

private void set(ThreadLocal key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

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

1. 取模运算获取到table数组的索引位置,从这个索引位置开始遍历到数组最后

2. 根据获取每个Entry的ThreadLocal引用,对于Entry它是个弱引用

static class Entry extends WeakReference<ThreadLocal> {

,获取到ThreadLocal

3. 如果key和k是指向同一个ThreadLocal,那么就将值设置到这个Entry上,返回

4. 不是同一个ThreadLocal,在判断下位置上的ThreadLocal是不是空的,因为Entry是弱引用,有可能这个ThreadLocal已经被垃圾回收了,如果ThreadLocal是空的,会去轮询找到下一个不为null的entry,将值放在这个entry,并且会去将过期的entry删除

5. 如果上面都没有返回的话,将entry数量加1 ,在索引位置设置一个新的Entry

get()

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
private Entry getEntry(ThreadLocal key) {
            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)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

1. 获取到当前线程绑定的ThreadLocalMap

2. 如果ThreadLocalMap不为空

  * 如果根据index能直接找到Entry,并且不为null,直接返回这个Entry

  * 否则,继续向下遍历,找到下一个不为null的entry,返回这个entry,并在轮询过程中将过期的entry删除

3. 如果ThreadLocalMap为空

给当前线程创建一个ThreadLocalMap,并设置初值

remove()

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

1 . 找到线程绑定的ThreadLocalMap,并且找到对应的entry,删除掉这个entry

总结

1. ThreadLocal不需要键值,因为ThreadLocalMap不是通过链表实现,而是通过开地址法实现的

2. 设置值的时候,如果index位置找到entry,并且key(这个key指的是ThreadLocal引用)是同一个,进行覆盖,否则向下找到一个不为null的entry,并设值

3. 查找数据的时候,如果能够从index找到entry,直接就返回了,否则就向下继续找到下一个不为null的entry,返回

4. 如果需要向ThreadLocal中存放不同类型的数据,需要定义多个ThreadLocal

参考博客:

http://www.cnblogs.com/xrq730/p/4854813.html












评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值