美文网首页
ThreadLocal因何而得藕

ThreadLocal因何而得藕

作者: 江北晓白 | 来源:发表于2019-10-04 21:41 被阅读0次

前面《Thread源码理解》一节讲了Thread的源码,因为是第一次写博客,难免有点拘泥于格式,本来想下面这张写一下我对线程池实现的理解,但是今天重新翻出了我的处女作,发现Thread源码中有ThreadLocal的变量:

#1
   /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

一直以来都是把ThreadLocal当作同一线程中顺序存取线程变量的容器,当看到Thread类中ThreadLocal变量=null且没有使用,则Thread类中定义ThreadLocals 变量是用来做什么的?后面会详细的分析ThreadLocals变量的用法。
一、ThreadLocalMap
首先,ThreadLocalMap是一个静态内部类,在ThreadLocal中定义了静态Map,此种设计属于组合模式(六原则一法则)。

(1)
static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
-------------------------------------------
(2)
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold; // Default to 0
private void setThreshold(int len) {
       threshold = len * 2 / 3;
}
--------------------------------------------
(3)
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);
        }

1、从上面的源码1中可以知道ThreadLocalMap是内部数组Entry为弱引用(WeakReference)的Map,且该引用为ThreadLocal<?>(弱引用为发生GC时清除对应对象,该处设计的很巧妙,认为线程执行时间小于两次gc间隔时间,则一个线程内数据是能够顺序获取的。)
2、从2中知道Entry初始大小为16,负载因子为2/3。
3、在3中主要是设置线程在ThreadLocalMap对应槽的值。
有这么一段代码:

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

因对应槽中是线程的资源作为key,当线程销毁时,对应的Entry[i]不会被销毁,而其对应的线程已经消失,此时需要清除这个Entry,而因为采用了弱引用,JVM会帮忙清除对应Entry(这也是1中所说的设计巧妙的地方,避免产生内存泄露)。
4、上面分析了Entry的实现,那现在有这么一种情况,当线程为线程池中的线程时,我们知道线程池中的线程terminated的状态后不会被销毁,当新的请求到来时会作为其work线程继续处理任务,那此时,如果该Entry的数据没有被GC,该ThreadLocal中存了上个请求的数据,所以当在线程池中使用ThreadLocal时,记得清除数据。
二、ThreadLocal的实现
一中对ThreadLocal中的Map的实现进行了分析,通过Map的槽存储了以ThreadLocal为key的键值对,那是如何实现的呢?
在ThreadLocal中有两个基本方法:
1)set方法

(1)
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
------------------------------------------------
(2)
 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
------------------------------------------------
(3)
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();
                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();
        }
------------------------------------------
(4)
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

1、(1)中getMap((Thread)t),第一次调用时,参考#1中Thread中ThreadLocals=nu l l,第一次获取map=null,调用(4)new ThreadLocalMap(this, firstValue),见一(3)分析。
2、后续set对应线程值时,直接对ThreadLocalMap的槽操作。
3、replaceStaleEntry、cleanSomeSlots这两个方法是用来处理key为null时清除脏数据,暂时先不分析。

2)get方法

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

get方法比较简单,就是获取桶中的对应ThreadLocal的value,如果为null就返回初始值(也是null,哈哈,点个赞吧)。
三、inheritableThreadLocals实现
前面讲了ThreadLocal的实现,同时也了解了Thread类中的ThreadLocals的作用和使用。在此基础上,来分析一下inheritableThreadLocals的实现。
以下面源码为引:

        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

上面是Thread中init的实现,当父inheritableThreadLocals 存在的时候,也就是子线程可继承时,调用ThreadLocal.createInheritedMap方法,从下面的源码中能够看到,子ThreadLocals初始化时,是对父inheritableThreadLocals的hash桶进行了完全复制:

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
-------------------------------------------------------
private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];
            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

相关文章

  • ThreadLocal因何而得藕

    前面《Thread源码理解》一节讲了Thread的源码,因为是第一次写博客,难免有点拘泥于格式,本来想下面这张写一...

  • 因荷而得藕?有杏不须梅

    因荷而得藕?有杏不须梅. 这是一个谐音对子:因荷(何)而得藕(偶)?有杏(幸)不须梅(媒)!典故如下:明朝礼部右...

  • 因荷而得藕

    摄影:SHILO设备:佳能7D

  • 因荷而得藕,只是不见梅。。

  • 旧时光 | 第一次的记忆~特别篇

    因荷(何)而得藕(偶)? 有杏(幸)不需梅(媒)! ——第一次当伴娘(纪实) 文 | 简若葉 2017年12月25...

  • 因……

    因何而起的因 因何而得的果 因何而躁动的心 因何而凌乱的颜 因何纠缠的缘与怨 因何在放与不放间彳亍不前 你我像两条...

  • 有杏不需梅,说说我与九三学社的情缘

    2014年我如愿加入了九三学社,有个老同学在QQ上留言“因荷而得藕?”询问我是何机缘加入九三,我回复“有杏不需梅!...

  • 因荷而得藕,有杏不需梅

    今天中午,我吃完饭后,就带云欣去睡午觉啦,今天云的还睡得挺早的,1: 40就睡了,要是之前嘛,她玩到三点钟,...

  • 因何而爱

    你会因为一个人喜欢上了一款饮料或美食

  • 因何而跑

    不知不觉,跑步已有一年整,依然是个跑渣渣。回头看这一年,也是感慨良多。 忘记当初为何走上runner这条不归路,只...

网友评论

      本文标题:ThreadLocal因何而得藕

      本文链接:https://www.haomeiwen.com/subject/zzqsuctx.html