美文网首页Java 杂谈
深入Java多线程--ThreadLocal

深入Java多线程--ThreadLocal

作者: JackFrost_fuzhu | 来源:发表于2019-01-17 09:42 被阅读4次

    文章结构:

    1)ThreadLocal介绍
    2)使用demo
    3)形象说明其原理
    4)源码阅读分析
    5)思考几个问题

    一、ThreadLocal介绍:

    ThreadLocal理解为线程本地变量。如果定义了一个ThreadLocal,每个线程往这个ThreadLocal中读写是线程隔离,互相之间不会影响的。它提供了一种将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制。

    核心:为了解决对象不能被多线程共享访问的问题。

    二、使用demo:

    public class ThreadLocalDemo {
        public static void main(String []args){
            for(int i=0;i<6;i++){
                final ThreadDemo t = new ThreadDemo();
                t.start();
                t.run();
            }
        }
        static class ThreadDemo extends Thread{
            //一个递增的序列,使用AtomicInger原子变量保证线程安全
            private static final AtomicInteger nextProperty = new AtomicInteger(0);
            //线程本地变量,为每个线程关联一个唯一的序号
            private static final ThreadLocal<Integer> property =
                    new ThreadLocal<Integer>() {
                        @Override
                        protected Integer initialValue() {
                            return nextProperty.getAndIncrement();
                        }
                    };
    
            //返回当前线程的唯一的序列,如果第一次get,会先调用initialValue,后面看源码就了解了
            public static int get() {
                return property.get();
            }
            @Override
            public void run(){
                System.out.println("当前线程:"+Thread.currentThread().getName()+",已分配ID:"+ get());
            }
        }
    }
    
    /*
    当前线程:main,已分配属性:0
    当前线程:main,已分配属性:0
    当前线程:Thread-3,已分配属性:2
    当前线程:Thread-1,已分配属性:1
    当前线程:Thread-0,已分配属性:3
    当前线程:Thread-5,已分配属性:4
    当前线程:Thread-2,已分配属性:5
    当前线程:Thread-4,已分配属性:6
    */
    

    三、形象说明其原理

    一个类:人的总称,一个记录所有人信息的系统
    身份证threadlocal:就是人的总称,在人的一个属性上为每个人记录东西,比如身份证
    一个线程:活生生的单独一个人
    这个线程的一个线程本地变量身份证:可以直接从身份证threadlocal获取到你的身份证

    四、源码阅读分析

    public class ThreadLocal<T> {
        
        /**
        调用nextHashCode()方法初始化threadLocalHashCode,且threadLocalHashCode初始化后不可变。threadLocalHashCode可用来标记不同的ThreadLocal实例。
        */
        private final int threadLocalHashCode = nextHashCode();
    
        /**
         * The next hash code to be given out. Updated atomically. Starts at
         * zero.
         */
        private static AtomicInteger nextHashCode =
            new AtomicInteger();
    
        /**
         * 生成hash code间隙为这个魔数,可以让生成出来的值或者说ThreadLocal的ID较为均匀地分布在2的幂大小的数组中
         */
        private static final int HASH_INCREMENT = 0x61c88647;
    
        /**
         * Returns the next hash code.
         */
        private static int nextHashCode() {
            return nextHashCode.getAndAdd(HASH_INCREMENT);
        }
    
        /**
         * 用户可以自定义这个函数实现,因此用户完全可以在这个函数里改变table。
         */
        protected T initialValue() {
            return null;
        }
    
        /**
         * JDK8新增,支持Lambda表达式,和ThreadLocal重写的initialValue()效果一样。
         */
        public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
            return new SuppliedThreadLocal<>(supplier);
        }
    
        /**
         * 构造函数
         */
        public ThreadLocal() {
        }
    
        /**
         * 返回当前线程的value
         */
        public T get() {
        // 获取当前线程
            Thread t = Thread.currentThread();
            // 获取当前线程对应的Map
            ThreadLocalMap map = getMap(t);
            // map不为空且当前线程有value,返回value
            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() {
        //调用重写的initialValue,返回新值
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            // 当前线程的ThreadLocalMap不为空,则直接赋值
            if (map != null)
                map.set(this, value);
            else
            // 为当前线程创造一个ThreadLocalMap(this, firstValue)并赋初值,this为当前线程
                createMap(t, value);
            return value;
        }
    
        public void set(T value) {
            Thread t = Thread.currentThread();
            //getMap(t)返回当前线程的成员变量ThreadLocalMap
            ThreadLocalMap map = getMap(t);
            //把成员变量设置进线程
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    
        /**
         * 获取当前线程的ThreadLocalMap,map不为空,则移除当前ThreadLocal作为key的键值对。
         * remove()移除当前线程的当前ThreadLocal数据(只是清空该key-value键值对),而且是立即移除,移除后,再调用get方法将重新调用initialValue方法初始化(除非在此期间调用了set方法赋值)。
         */
         public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
    
        /**
         * getMap(t)返回当前线程的成员变量ThreadLocalMap,Thread的成员变量有ThreadLocalMap
         * @param  t the current thread
         * @return the map
         */
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
        /**
         * 如果当前线程没有ThreadLocalMap,会先创建一个
         */
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    
        /**
         * 从父线程的inheritableThreadLocals中把有效的entry都拷过来
         */
        static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
            return new ThreadLocalMap(parentMap);
        }
    
        /**
         * Method childValue is visibly defined in subclass
         * InheritableThreadLocal, but is internally defined here for the
         * sake of providing createInheritedMap factory method without
         * needing to subclass the map class in InheritableThreadLocal.
         * This technique is preferable to the alternative of embedding
         * instanceof tests in methods.
         */
        T childValue(T parentValue) {
            throw new UnsupportedOperationException();
        }
    
        /**
         * SuppliedThreadLocal是JDK8新增的内部类,只是扩展了ThreadLocal的初始化值的方法而已,允许使用JDK8新增的Lambda表达式赋值。需要注意的是,函数式接口Supplier不允许为null。
         */
        static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
    
            private final Supplier<? extends T> supplier;
    
            SuppliedThreadLocal(Supplier<? extends T> supplier) {
                this.supplier = Objects.requireNonNull(supplier);
            }
    
            @Override
            protected T initialValue() {
                return supplier.get();
            }
        }
    
        /**
         * ThreadLocalMap is a customized hash map suitable only for
         * maintaining thread local values. No operations are exported
         * outside of the ThreadLocal class. The class is package private to
         * allow declaration of fields in class Thread.  To help deal with
         * very large and long-lived usages, the hash table entries use
         * WeakReferences for keys. However, since reference queues are not
         * used, stale entries are guaranteed to be removed only when
         * the table starts running out of space.
         */
        static class ThreadLocalMap {
    
            /**
             * 用于存储数据
             * 每个Entry对象都有一个ThreadLocal的弱引用(作为key),这是为了防止内存泄露。一旦线程结束,key变为一个不可达的对象,这个Entry就可以被GC了。
             */
            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;
    
            /**
             * Entry表,大小必须为2的幂
             */
            private Entry[] table;
    
            /**
             * The number of entries in the table.
             */
            private int size = 0;
    
            /**
             * 重新分配表大小的阈值,默认为0
             */
            private int threshold; // Default to 0
    
            /**
             * 设置resize阈值以维持最坏2/3的装载因子
             */
            private void setThreshold(int len) {
                threshold = len * 2 / 3;
            }
    
            /**
             * 环形意义的下一个索引
             */
            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);
            }
    
            /**
             * Construct a new map initially containing (firstKey, firstValue).
             * ThreadLocalMap是惰性构造的,所以只有当至少要往里面放一个元素的时候才会构建它
             */
            ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
                // 初始化table数组
                table = new Entry[INITIAL_CAPACITY];
                // 用firstKey的threadLocalHashCode与初始大小16取模得到哈希值
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                // 初始化该节点
                table[i] = new Entry(firstKey, firstValue);
                // 设置节点表大小为1
                size = 1;
                // 设定扩容阈值
                setThreshold(INITIAL_CAPACITY);
            }
    
            /**
             * Construct a new map including all Inheritable ThreadLocals
             * from given parent map. Called only by createInheritedMap.
             *
             * @param parentMap the map associated with parent thread.
             */
            private ThreadLocalMap(ThreadLocalMap parentMap) {
                Entry[] parentTable = parentMap.table;
                int len = parentTable.length;
                //设置resize阈值以维持最坏2/3的装载因子
                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) {
                        //childValue方法在InheritableThreadLocal中默认实现为返回本身值,可以被重写
                            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++;
                        }
                    }
                }
            }
    
            /**
             * Get the entry associated with key.  This method
             * itself handles only the fast path: a direct hit of existing
             * key. It otherwise relays to getEntryAfterMiss.  This is
             * designed to maximize performance for direct hits, in part
             * by making this method readily inlinable.
             *
             * @param  key the thread local object
             * @return the entry associated with key, or null if no such
             */
            private Entry getEntry(ThreadLocal<?> key) {
            // 根据key这个ThreadLocal的ID来获取索引,也即哈希值
                int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                // 对应的entry存在且未失效且弱引用指向的ThreadLocal就是key,则命中返回
                if (e != null && e.get() == key)
                    return e;
                else
                    //线性探测,所以往后找还是有可能能够找到目标Entry的
                    return getEntryAfterMiss(key, i, e);
            }
    
            /**
             * 调用getEntry未直接命中的时候调用此方法
             */
            private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
                Entry[] tab = table;
                int len = tab.length;
                // 基于线性探测法不断向后探测直到遇到空entry
                while (e != null) {
                    ThreadLocal<?> k = e.get();
                    // 找到目标
                    if (k == key)
                        return e;
                    if (k == null)
                        // 该entry对应的ThreadLocal已经被回收,调用expungeStaleEntry来清理无效的entry
                        expungeStaleEntry(i);
                    else
                        // 环形设计下往后面走
                        i = nextIndex(i, len);
                    e = tab[i];
                }
                return null;
            }
    
            /**
             * Set the value associated with key.
             *  set方法
             * @param key the thread local object
             * @param value the value to be set
             */
            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();
                    // 找到对应的entry
                    if (k == key) {
                        e.value = value;
                        return;
                    }
                    // 替换失效的entry
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }
    
            /**
             * 从map中删除ThreadLocal的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();
                        // 进行Entry清理
                        expungeStaleEntry(i);
                        return;
                    }
                }
            }
    
            /**
                    处理当前已知的脏entry,它认为在出现脏entry的相邻位置也有很大概率出现脏entry,所以为了一次处理到位,就需要向前环形搜索,找到前面的脏entry。
             */
            private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                           int staleSlot) {
                Entry[] tab = table;
                int len = tab.length;
                Entry e;
    
                int slotToExpunge = staleSlot;
                // 向前扫描,查找最前的一个无效slot
                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
                 // 向后遍历table
                for (int i = nextIndex(staleSlot, len);
                     (e = tab[i]) != null;
                     i = nextIndex(i, len)) {
                    ThreadLocal<?> k = e.get();
                        // 找到了key,将其与无效的slot交换
                    if (k == key) {
                    // 更新对应slot的value值
                        e.value = value;
    
                        tab[i] = tab[staleSlot];
                        tab[staleSlot] = e;
    
                        /*
                         * 如果在整个扫描过程中(包括函数一开始的向前扫描与i之前的向后扫描)
                         * 找到了之前的无效slot则以那个位置作为清理的起点,
                         * 否则则以当前的i作为清理起点
                         */
                        if (slotToExpunge == staleSlot)
                            slotToExpunge = i;
                        
                         // 从slotToExpunge开始做一次连续段的清理,再做一次启发式清理                   cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                        return;
                    }
    
                    // 如果当前的slot已经无效,并且向前扫描过程中没有无效slot,则更新slotToExpunge为当前位置
                    if (k == null && slotToExpunge == staleSlot)
                        slotToExpunge = i;
                }
    
                 // 如果key在table中不存在,则在原地放一个
                tab[staleSlot].value = null;
                tab[staleSlot] = new Entry(key, value);
    
                // If there are any other stale entries in run, expunge them
                // 在探测过程中如果发现任何无效slot,则做一次清理
                if (slotToExpunge != staleSlot)
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            }
    
            /**
             * ThreadLocal中核心清理函数,它做的事情:
             * 从staleSlot开始遍历,将无效(弱引用指向对象被回收)清理,即对应entry中的value置为null,将指向这个entry的table[i]置为null,直到扫到空entry。
             * 另外,在过程中还会对非空的entry作rehash。
             */
            private int expungeStaleEntry(int staleSlot) {
                Entry[] tab = table;
                int len = tab.length;
    
                // 因为entry对应的ThreadLocal已经被回收,value设为null,显式断开强引用
                tab[staleSlot].value = null;
                // 显式设置该entry为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();
                    // 清理对应ThreadLocal已经被回收的entry
                    if (k == null) {
                        e.value = null;
                        tab[i] = null;
                        size--;
                    } else {
                    /*
                     * 对于还没有被回收的情况,需要做一次rehash。
                     * 
                     * 如果对应的ThreadLocal的ID对len取模出来的索引h不为当前位置i,
                     * 则从h向后线性探测到第一个空的slot,把当前的entry给挪过去。
                     */
                        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;
            }
    
            /**
             * Heuristically scan some cells looking for stale entries.
             * This is invoked when either a new element is added, or
             * another stale one has been expunged. It performs a
             * logarithmic number of scans, as a balance between no
             * scanning (fast but retains garbage) and a number of scans
             * proportional to number of elements, that would find all
             * garbage but would cause some insertions to take O(n) time.
             *
             * @param i a position known NOT to hold a stale entry. The
             * scan starts at the element after i.
             *
             * @param n scan control: {@code log2(n)} cells are scanned,
             * unless a stale entry is found, in which case
             * {@code log2(table.length)-1} additional cells are scanned.
             * When called from insertions, this parameter is the number
             * of elements, but when from replaceStaleEntry, it is the
             * table length. (Note: all this could be changed to be either
             * more or less aggressive by weighting n instead of just
             * using straight log n. But this version is simple, fast, and
             * seems to work well.)
             *
             * @return true if any stale entries have been removed.
                主动式清理slot
                i对应entry是非无效(指向的ThreadLocal没被回收,或者entry本身为空)
                n是用于控制控制扫描次数的。
                
                正常情况下如果log n次扫描没有发现无效slot,函数就结束了
                但是如果发现了无效的slot,将n置为table的长度len,做一次连续段的清理
                再从下一个空的slot开始继续扫描
    
                这个函数有两处地方会被调用,一处是插入的时候可能会被调用,另外个是在替换无效slot的时候可能会被调用,区别是前者传入的n为元素个数,后者为table的容量
    
    
             */
            private boolean cleanSomeSlots(int i, int n) {
                boolean removed = false;
                Entry[] tab = table;
                int len = tab.length;
                do {
                // i在任何情况下自己都不会是一个无效slot,所以从下一个开始判断
                    i = nextIndex(i, len);
                    Entry e = tab[i];
                    if (e != null && e.get() == null) {
                    // 扩大扫描控制因子
                        n = len;
                        removed = true;
                         // 清理一个连续段
                        i = expungeStaleEntry(i);
                    }
                } while ( (n >>>= 1) != 0);
                return removed;
            }
    
            /**
             * Re-pack and/or re-size the table. First scan the entire
             * table removing stale entries. If this doesn't sufficiently
             * shrink the size of the table, double the table size.
             */
            private void rehash() {
                // 做全量清理
                expungeStaleEntries();
    
                // 刚做完一次清理,size可能会变小,所以需要判断是否扩容
                if (size >= threshold - threshold / 4)
                    resize();
            }
    
            /**
             *  扩容,因为需要保证table的容量len为2的幂,所以扩容即扩大2倍
             */
            private void resize() {
                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) {
                            e.value = null; // Help the GC
                        } else {
                            int h = k.threadLocalHashCode & (newLen - 1);
                            while (newTab[h] != null)
                                h = nextIndex(h, newLen);
                            newTab[h] = e;
                            count++;
                        }
                    }
                }
    
                setThreshold(newLen);
                size = count;
                table = newTab;
            }
    
            /**
             * Expunge all stale entries in the table.
             * 做全量清理
             */
            private void expungeStaleEntries() {
                Entry[] tab = table;
                int len = tab.length;
                for (int j = 0; j < len; j++) {
                    Entry e = tab[j];
                    if (e != null && e.get() == null)
                        expungeStaleEntry(j);
                }
            }
        }
    }
    
    

    五、思考几个问题

    (1)存储设计

    ThreadLocalMap真的是一个map吗?

    1)我们首先看到Entry的设计,是继承了弱引用,弱引用一会再讨论。并且看到ThreadLocalMap为各个线程存储变量的数组。是数组!不是map。

    static class ThreadLocalMap {
            static class Entry extends WeakReference<ThreadLocal<?>> {
                //ThreadLocal存储值
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
            //ThreadLocal存储数组
             private Entry[] table;
     }
    

    2)然后我们要看set、get的操作

    主要看到的是一个线性探测法,遍历table数组,而不是map!

    private Entry getEntry(ThreadLocal<?> key) {
            // 根据key这个ThreadLocal的ID来获取索引,也即哈希值
                int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                // 对应的entry存在且未失效且弱引用指向的ThreadLocal就是key,则命中返回
                if (e != null && e.get() == key)
                    return e;
                else
                    //线性探测,所以往后找还是有可能能够找到目标Entry的
                    return getEntryAfterMiss(key, i, e);
    }
    
    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();
                    // 找到对应的entry
                    if (k == key) {
                        e.value = value;
                        return;
                    }
                    // 替换失效的entry
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
    }
    
    

    (2)弱引用设计与内存泄露问题

    1)首先是为什么Entry设计成弱引用?

    ThreadLocal的设计是用数组存储Entry,实质上就会造成节点的生命周期与线程强绑定,只要线程没有销毁,那么节点在GC分析中一直处于可达状态,没办法被回收,而程序本身也无法判断是否可以清理节点。

    2)然后我们要清晰Java的引用

    强引用
    就是指在程序代码之中普遍存在的,类似Object obj = new Object()这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

    软引用
    是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了SoftReference类来实现软引用。

    弱引用
    也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。

    虚引用
    也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。

    3)ThreadLocal的引用选型:

    弱引用是Java中四档引用的第三档,比软引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次GC。当某个ThreadLocal已经没有强引用可达,则随着它被垃圾回收,在ThreadLocalMap里对应的Entry的键值会失效,这为ThreadLocalMap本身的垃圾清理提供了便利

    4)接着分析Entry的引用链:

    当前线程->当前线程的threadLocals(ThreadLocal.ThreadLocalMap对象)->Entry数组->entry.value
    这样一条强引用链是可达的,那么直到线程结束前,Entry中的value都是无法回收的。

    5)内存泄露分析

    Entry类继承了WeakReference<ThreadLocal<?>>,即每个Entry对象都有一个ThreadLocal的弱引用(作为key),这是为了防止内存泄露。一旦线程结束,key变为一个不可达的对象,这个Entry就可以被GC了。

    但是我们刚刚分析引用链也可以看到,还是可能存在一条强引用链,导致不会给回收的。那为啥还是这样去设计?

    然后我们又要看回源码。
    在threadLocal的生命周期里(set,getEntry,remove)里,都会针对key为null的脏entry进行处理。当然只是有很高概率会顺便清理这些脏key。
    所以使用弱引用的话在threadLocal生命周期里会尽可能的保证不出现内存泄漏的问题,达到安全的状态。

    6)建议:

    所以,我们在使用threadlocal的时候,尽量在业务流程的最后,显式清除这个key。

    (3)ThreadLocalMap和HashMap在hash冲突时的解决方案对比:

    HashMap:若冲突则将新数据按链表或红黑树逻辑插入。
    具体可阅读此文章:https://blog.csdn.net/Jack__Frost/article/details/69388422

    而ThreadLocalMap,采用的线性探测法,其实就是数组的遍历。

    (4)局部变量与ThreadLocal的技术选型:

    很多时候ThreadLocal我们可以使用局部变量进行替代就可以,简单快捷方便,但是为什么还采用ThreadLocal,比如SimpleDateFormat对象使用ThreadLocal??

    频繁利用局部变量的创建导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接或者new SimpleDateFormat对象,这样不仅严重影响程序执行效率,还可能导致服务器压力巨大。

    所以说,这种情况下使用ThreadLocal是再适合不过的了,因为ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能

    (5)相比于同步机制,threadlocal解决了什么问题

    它们相同点都是为了解决多线程中变量访问冲突的问题。
    但是他们解决的思路不一样,一个是时间换空间,一个是空间换时间。

    对于同步机制

    在同步机制中,通过锁机制来保证变量在同一个时间点只能被一个线程所持有。这个时候,该变量在多个线程间是共享的。

    对于threadlocal

    ThreadLocal则从另一个方面解决多线程变量并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。

    总结

    对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

    (6)使用场景

    1)SimpleDateFormat的线程安全管理

    为什么这个要利用ThreadLocal?
    1- 性能问题,SimpleDateFormat是一个比较重的对象,而且对应格式是可以重复利用的。
    2- 线程安全问题,SimpleDateFormat并不是线程安全对象。

    2) 数据库连接、Session管理

    每次获得connection都需要浪费cpu资源和内存资源,是很浪费资源的。所以诞生了数据库连接池、session管理池。

    总体思路:
    getConnection(),都是先从threadlocal里面拿的,如果threadlocal里面有,则用,保证线程里的多个dao操作,用的是同一个connection,以保证事务。如果新线程,则将新的connection放在threadlocal里,再get给到线程。

    将connection放进threadlocal里的,以保证每个线程从连接池中获得的都是线程自己的connection。


    问题待续......

    更多文章点此处

    相关文章

      网友评论

        本文标题:深入Java多线程--ThreadLocal

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