美文网首页
ThreadLocal记录

ThreadLocal记录

作者: 神棄丶Aria | 来源:发表于2020-03-24 12:07 被阅读0次

    一、概念

    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

    相关文章

      网友评论

          本文标题:ThreadLocal记录

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