ThreadLocal源码解析

ThreadLocal有内部类ThreadLocalMap,ThreadLocalMap是ThreadLocal的核心
1.每个线程下的有一个ThreadLocalMap
static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

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

    /**
     * The initial capacity -- MUST be a power of two.
     */
    private static final int INITIAL_CAPACITY = 16;

    //底层是个数组,用于存储具体的值
    //Entry的key是ThreadLocal -> weakReference引用
    //Entry的value是具体的值
    private Entry[] table;

    /**
     * The number of entries in the table.
     */
    private int size = 0;

    /**
     * The next size value at which to resize.
     */
    private int threshold; // Default to 0

    /**
     * Set the resize threshold to maintain at worst a 2/3 load factor.
     */
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }

    /**
     * Increment i modulo len.
     */
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

    /**
     * Decrement i modulo len.
     */
    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }
    
    //设置当前线程值
    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        
        //获取当前(Thread)线程下的Map
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //key.threadLocalHashCode & (len-1); 算出数组下标位置
            //tmp[i] = new Entry(ThreadLocal,value);
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }
    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);
    }

    /**
     * Set the value associated with key.
     *
     * @param key the thread local object
     * @param value the value to be set
     */
    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();
    }

    /**
     * Remove the entry for key.
     */
    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;
            }
        }
    }

    /**
     * Replace a stale entry encountered during a set operation
     * with an entry for the specified key.  The value passed in
     * the value parameter is stored in the entry, whether or not
     * an entry already exists for the specified key.
     *
     * As a side effect, this method expunges all stale entries in the
     * "run" containing the stale entry.  (A run is a sequence of entries
     * between two null slots.)
     *
     * @param  key the key
     * @param  value the value to be associated with key
     * @param  staleSlot index of the first stale entry encountered while
     *         searching for key.
     */
    private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                   int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;
        Entry e;

        // Back up to check for prior stale entry in current run.
        // We clean out whole runs at a time to avoid continual
        // incremental rehashing due to garbage collector freeing
        // up refs in bunches (i.e., whenever the collector runs).
        int slotToExpunge = staleSlot;
        for (int i = prevIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = prevIndex(i, len))
            if (e.get() == null)
                slotToExpunge = i;

        // Find either the key or trailing null slot of run, whichever
        // occurs first
        for (int i = nextIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();

            // If we find key, then we need to swap it
            // with the stale entry to maintain hash table order.
            // The newly stale slot, or any other stale slot
            // encountered above it, can then be sent to expungeStaleEntry
            // to remove or rehash all of the other entries in run.
            if (k == key) {
                e.value = value;

                tab[i] = tab[staleSlot];
                tab[staleSlot] = e;

                // Start expunge at preceding stale entry if it exists
                if (slotToExpunge == staleSlot)
                    slotToExpunge = i;
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                return;
            }

            // If we didn't find stale entry on backward scan, the
            // first stale entry seen while scanning for key is the
            // first still present in the run.
            if (k == null && slotToExpunge == staleSlot)
                slotToExpunge = i;
        }

        // If key not found, put new entry in stale slot
        tab[staleSlot].value = null;
        tab[staleSlot] = new Entry(key, value);

        // If there are any other stale entries in run, expunge them
        if (slotToExpunge != staleSlot)
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
    }
}

 
查看以上的代码可以总结出:

1.每个Thread都有一个成员变量

2.设置ThreadLocal.set的时候,会调用Thread t = Thread.currentThread();

3.拿到当前Thread后,就拿当前的Thread成员变量threadLocals

4.然后通过key.threadLocalHashCode & (len-1);。设置[]entry数组下标位置

5.然后通过开放寻址法,进行计算下标位置有冲突则再次计算下标位置,直到不重复。
 
系统
 
├── Thread1
 
│ ├── threadLocals (ThreadLocalMap 类型)
 
│ │ └── Entry[]
 
│ │ ├── Entry1
 
│ │ │ ├── key: ThreadLocal1 (弱引用)
 
│ │ │ └── value: 线程局部变量值1_Thread1
 
│ │ ├── Entry2
 
│ │ │ ├── key: ThreadLocal2 (弱引用)
 
│ │ │ └── value: 线程局部变量值2_Thread1
 
│ │ └── ...
 
│ └── 其他线程属性...
 
├── Thread2
 
│ ├── threadLocals (ThreadLocalMap 类型)
 
│ │ └── Entry[]
 
│ │ ├── Entry1
 
│ │ │ ├── key: ThreadLocal1 (弱引用)
 
│ │ │ └── value: 线程局部变量值1_Thread2(可能与Thread1中的值不同)
 
│ │ ├── Entry2
 
│ │ │ (可能不存在,因为Thread2可能没有使用ThreadLocal2)
 
│ │ └── ...
 
│ └── 其他线程属性...
 
└── ...(其他线程)
 
 
 
ThreadLocal
 
├── set 方法
 
│ └── 在当前线程的 ThreadLocalMap 中设置键值对
 
└── get 方法
 
└── 在当前线程的 ThreadLocalMap 中根据 ThreadLocal 对象作为键查找并返回对应的值

 

posted @   辉辉、  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示