ThreadLocal记录

2020-03-24  本文已影响0人  神棄丶Aria

一、概念

ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。

二、ThreadLocalMap

线程实例的局部变量,用于存储该线程下的ThreadLocal变量

Thread.java
public class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
}


ThreadLocalMap.class
    static class ThreadLocalMap {
        private static final int INITIAL_CAPACITY = 16;
        private ThreadLocal.ThreadLocalMap.Entry[] table;
        private int size;
        private int threshold;

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

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

        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、ThreadLocalMap下保存table数组,该数组用于存储当前线程下的所有ThreadLocal变量。且Entry为弱引用,因此不怕内存泄漏问题。
·2、threshold为长度阈值,超过该值则需要扩容table,阈值计算为当前长度的2/3。

1、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();
            //TODO 步骤1
                if (k == key) {
                    e.value = value;
                    return;
                }
            //TODO 步骤2
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //TODO 步骤3
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

·1、set()方法中,会遍历当前的table,首先判断传入的key值是否存在,如果是则更新entry.value然后return
·2、如果找到当前的entry不为空但key为空(可能是ThreadLocal被回收了)则表示该数据无用,则通过replaceStaleEntry更新数据然后return
·3、遍历后无发现合适的位置则插入新Entry,插入后对当前table做清除扩容操作
(1)cleanSomeSlots()主要就是将那些key不为null,但是value为空的调用expungeStaleEntry(i)清除掉,如果清除了就是返回ture否则返回false
(2)rehash()就是先调用expungeStaleEntry(),然后再根据if (size >= threshold - threshold / 4)去决定要不要resize()
(3)reszie()扩容,清除无用数据,同时生成新key值与生成新阈值【不想看了。】

2、getEntry()
        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、get的逻辑很好懂,首先通过ThreadLocal的换算哈希值判断当前位置是否就是所要的结果,如果不是则以该哈希值为原点遍历table,遍历过程中同时清除不合理的Entry,如果无值则返回null。

三、ThreadLocal

ThreadLocal通过从Thread中获取threadLocals,为ThreadLocalMap对象,该map以当前ThreadLocal为key来保存存储的值。


image.png
1、set()
    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、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();
    }

    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    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;
    }

·1、首先查询ThreadLocalMap中是否有该值,如果entry不为null则返回entry.value
·2、如果当前map为null或entry为null,则为该map设置初始值。在setInitialValue中会创建ThreadLocalMap,并通过initialValue()获取value的默认值,该方法个人认为是留下一个可扩展的地方。

参考文章

https://www.jianshu.com/p/fb46395f4551

上一篇下一篇

猜你喜欢

热点阅读