ThreadLocal源码解析

多线程并发访问临界资源,破坏原子操作,会导致线程安全问题。将公共变量与ThreadLocal进行绑定,线程使用该公共变量时拿到的是在该线程中的副本变量,即将线程中的数据进行了隔离,多个线程能使用该变量但互不影响。

变量值的存放路径为:Thread->ThreadLocalMap->Entry->value,ThreadLocal作为Entry的key,通过key可以拿到value。

ThreadLocalMap的数据结构示意图:

 

1.成员变量

内部类ThreadLocalMap 中的Entry[] table 数组(数据结构),table的长度必须是2的n次方,size表示map中entry的个数,threshold表示扩容阈值。阈值通常为2/3*length。

ThreadLocalMap中内部类Entry的value表示线程存储的副本变量值。Entry继承WeakReference,Entry的key是ThreadLocal,是弱引用,在垃圾回收时被回收。

 

2.构造方法

无参构造

 

3.查找元素

public T get() {
        //获取当前执行线程
        Thread t = Thread.currentThread();
        //获取当前线程的成员变量threadLocals
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //通过key threadLocal获取到线程的副本变量value
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
private Entry getEntry(ThreadLocal<?> key) {
            //通过key值的hash值找到元素存储的索引位置,这个和hashmap的思想是一致的
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            //注意要对key做判断,经常存在e不为空,e.get()为null的情况,这种情况存在内存泄露的风险
            if (e != null && e.get() == key)
                return e;
            else
                //未找到entry时,往后查找清除key为null的entry对象
                return getEntryAfterMiss(key, i, e);
        }

1.获取当前执行线程thread。

2.获取线程中的变量threadLocalMap。

3.通过threadLocal在map中获取对应的entry。通过key的hash值找到元素在桶中的位置,特别注意需要对entry key的判断,经常存在entry不为空,key为空的情况(key为弱引用)。

4.返回对应的value值。

5.threadlocal是弱引用WeakReference,当gc运行时就会被回收。SoftReference指的是软引用,内存不存+gc运行时对象会被回收。Stong Reference指的是强引用,obj=null(手动置null)+gc运行时对象会被回收,大多数创建的对象都是强引用对象。

 

4.插入元素

插入元素的几种场景:假设插入的key(threadlocal)经hashcode计算,在map中的位置为i。

1)假如table[i]==null,直接插入到i处。否则发生了hash冲突。进入步骤2)

2)entry.get()==key,相同的key,更新entry的value值。否则进入步骤3)

3)k!=null时(entry有效),i=i+1 重新进入1)。k == null(相等则表示当前entry的弱引用threadlocal被垃圾回收了),查找当前位置的后续位置,如果entry.get()==key表示key在map中存在,更新value值,否则进入步骤4)

4)将当前索引处的entry置为null,创建新的entry存放在此处,new Entry(key,value)

set和remove操作涉及到了无效entry的清除工作和重建hash,使用entry的存放位置尽量保持hash存储的规则。

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //当前线程将value值绑定到threadLocal
            map.set(this, value);
        else
            createMap(t, value);
    }
private void set(ThreadLocal<?> key, Object value) {
            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();

                //找到相同的key,更新value值
                if (k == key) {
                    e.value = value;
                    return;
                }

                //插入位置元素存在但是key为null,此时的entry是无效的,做替换处理
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //待插入位置没有元素直接插入。存在元素则插入到下一个最靠近此处的索引。
            tab[i] = new Entry(key, value);
            int sz = ++size;
            //未清除到key为null的entry对象且map中entry个数大于扩容阈值时
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
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).
            //往前查找key为null的无效entry
            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
            //往后查找entry,若之前发生过hash冲突,相同的key可能在后面的索引处
            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.
                //找到了key相同的entry,替换entry的索引位置,清除无效的entry对象
                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
            //未找到相同的key,将要插入的key,value存储到当前索引的entry,清除无效的entry对象
            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);
        }
private void rehash() {
            //清除table中所有key为null的entry
            expungeStaleEntries();

            // Use lower threshold for doubling to avoid hysteresis
            if (size >= threshold - threshold / 4)
                //扩容
                resize();
        }

1.同上

2.同上

3.当插入索引处存在值的时候,即发生了hash冲突,key不能重复原则。(1)若key相同则更新value值,(2)若key为null时,向后查找过程中若存在相同的key(之前插入时发生了hash冲突,导致元素存储在下一个索引处)则更新value值并且进行元素索引位置的替换。若没有找到key,则更新当前索引处entry为新插入的值。清除无效entry时,起点是slotToExpunge,以当前索引为中心,向前后查找一组需要清除的entry对象进行清除操作,前后边界是桶中元素为null。(3)否则元素插入的位置是下一个最靠近当前索引的位置。

4.当指定索引处无值的时候直接新增一个entry对象。

5.当没有清除到key=null的entry对象且size>threshold时进行rehash()操作。

6. rehash()操作,清除table中所有key=null的entry对象,size>0.75threshold时进行扩容。

 

5.移除元素

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) {
                    //key置为null
                    e.clear();
                    //value置为null
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

1.同上

2.同上

3.将key置为null。

4.清除无效的entry对象(将value置为null)。

 

6.扩容

private void resize() {
            //数组长度变为原来的2倍
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        //清除key为null的entry
                        e.value = null; // Help the GC
                    } else {
                        //数组元素赋值时,若存在hash冲突,插入索引为下一个最近的索引
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }
            //设置扩容阈值
            setThreshold(newLen);
            size = count;
            table = newTab;
        }

1.数组容量变为原来的两倍。

       2.清除key=null的entry对象。

       3.元素在放入新的数组时,若发生了hash冲突,则元素插入到下一个最近的索引处。

 

7.清除无效的entry对象

private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            //清除当前索引的无效entry
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    //当前索引往后清除无效entry
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    //若元素的存储索引i和应当存储的索引h不相同时,可能是元素插入时
                    //发生了hash冲突。因此将该元素重新放入到索引h处或下一个最靠近h的位置
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

private int expungeStaleEntry(int staleSlot) {}

staleSlot索引处entry对应的key=null。返回值是staleSlot往后entry为null的索引。

1.清除staleSlot索引处的entry对象,

2.进行rehash操作,直到entry为null。staleSlot往后查找,当entry的key=null时,将该处的entry对象置为空,否则当entry现在所在的索引位置i与应当在索引位置h不同,将entry调整到索引h位置,或者最接近h的位置。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值