zoukankan      html  css  js  c++  java
  • ThreadLocal本地线程深入理解及线程间请求参数token传递

    结论:内部通过,操作当前线程的成员变量threadLocalMap,即Thread.currentThread.threadLocalMap,Map中key为当前线程的threadLocal对象(即调用add()方法的实例对象this,本文中为threadLocalUser),value为存入对象,当存取时内部自动传入当前线程key,所以只能取到当前线程的绑定变量,从而实现线程级隔离。当线程不同时操作的thread也不同,Map当然也不同,map中的key和value也就不同了。

    打个不太恰当的比喻,多个It男(Thread),每个人一个背包(threadLocalMap),包里放入自己经常要用的东西比如笔记本(key标签为"笔忘本",value"联想电脑",没有的话,第一次参加工作createMap要用的时候也会买了放进去),当多个人同时都接到接到任务“拿出笔记本”(并发http请求),自己在任何地方都可以把“笔忘本”拿出来用,但每个人都不会拿错。

     注:map的key不是线程id或者name,而是当前线程下的ThreadLocal实例对象,之前所以这么设计,保证当前线程可以有多个ThreadLocal对象,如果key为当前线程,那就限制了当前线程只能存储一个对象

    ThreadLocal<Student> threadLocalStudent=new ThreadLocal();

    ThreadLocal<UserInfo> threadLocalUserInfo=new new ThreadLocal();

    为方便分析本文以一个ThreadLocal对象来举例,并且为方便线程内跨类跨包随取随用,用自定义类MyThreadLocal来将ThreadLocal成员封装为静态对象,并为之提供访问方法set ,get

    1.作用:

    用于存储线程中共享变量,随用随取,如操作员信息,一处存入处处可用

    2.原理如下

     存:Class加载初始化时将threadlocal变量加入方法区(线程共享),存的时候直接MyThreadLocal.threadLocal.set(new UserInfo("1001","zhangsan"));内部threadlocal通过当前线程T找到线程的变量threadLocalMap,然后put(key(线程的ThreadLocal实例对象),value(UserInfo对象))存入Map

    取:UserInfo user=MyThreadLocal.threadLocal.get();内部也是通过当前线程绑定的threadLocalMap中,get(key(当前线程的ThreadLocal实例对象)),所以只要线程相同且ThreadLocal实例ID相同,取到的值就相同,反之则不同,从而实现不同线程间隔离,同一线程中共享。

    3.跟踪调试

    自定义类MyThreadLocal中,threadLocal在MyThreadLocal初始化时就有值,且只初始化一次,与类相关与实例对象无关,所以任何线程进来都是一个值

    在ThreadLocal的set方法源码中打断点,跟踪Thread t的结构,t 中的成员变量threadLocals即为存储Map,map中有table(Entry数组,元素Entry.key 为线程的ThreadLocal实例对象的弱引用referent,Entry.value为存入对象),第63个元素的value就是存入对象,referent为当前线程的引用

    注意:线程Id不同时,本地线程中的变量为null,必须自己用自己取

     referent的类型为ThreadLocal

     

    第63元素的referent为下图

     

     4.多线程传传参数

    先将本地变量取出,再在新线程中传入,如果要传token,只需在userInfo中添加一个字段,请求进入时在controller或者拦截器中MyThreadLocal.add(userInfo)即可后续使用。

              //先取出当前值  
          UserInfo userInfo= MyThreadLocal.getUserInfo();
          
    //推荐使用线程池创建新线程
    new Thread(()->{ log.info("before setting,"+JSONObject.toJSONString(MyThreadLocal.getUserInfo()));//返回NULL MyThreadLocal.add(sysOperDTO); log.info("after setting,=="+JSONObject.toJSONString(MyThreadLocal.getUserInfo()));//返回对象 }).start();
          

    UserInfo类

    @Data
    public class UserInfo {
        private String threadName;
        private String threadId;
    
    }

    MyThreadLocal

    public class MyThreadLocal {
    
        private MyThreadLocal() {
        }
    
        private static final ThreadLocal<UserInfo> threadLocalUser = new ThreadLocal<>();
    
        public static void add(UserInfo userInfo) {
            userInfo.setThreadName(Thread.currentThread().getName());
            userInfo.setThreadId("threadId:"+String.valueOf(Thread.currentThread().getId())+" threadLocalUser.hashCode=="+ threadLocalUser.hashCode());
            threadLocalUser.set(userInfo);
        }
    
        public static UserInfo getUserInfo() {
            return threadLocalUser.get();
        }
    
        public static void remove() {
            threadLocalUser.remove();
        }
    }

    5.Map实际结构,是ThreadLocal的静态内部类

       /**
         * 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 {
    
            /**
             * The entries in this hash map extend WeakReference, using
             * its main ref field as the key (which is always a
             * ThreadLocal object).  Note that null keys (i.e. entry.get()
             * == null) mean that the key is no longer referenced, so the
             * entry can be expunged from table.  Such entries are referred to
             * as "stale entries" in the code that follows.
             */
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    
            /**
             * The initial capacity -- MUST be a power of two.
             */
            private static final int INITIAL_CAPACITY = 16;
    
            /**
             * The table, resized as necessary.
             * table.length MUST always be a power of two.
             */
            private Entry[] table;
    
            /**
             * The number of entries in the table.
             */
            private int size = 0;
    
            /**
             * The next size value at which to resize.
             */
            private int threshold; // Default to 0
    
            /**
             * Set the resize threshold to maintain at worst a 2/3 load factor.
             */
            private void setThreshold(int len) {
                threshold = len * 2 / 3;
            }
    
            /**
             * Increment i modulo len.
             */
            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).
             * ThreadLocalMaps are constructed lazily, so we only create
             * one when we have at least one entry to put in it.
             */
            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);
            }
    
            /**
             * 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;
                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++;
                        }
                    }
                }
            }
    
            /**
             * 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) {
                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);
            }
    
            /**
             * Version of getEntry method for use when key is not found in
             * its direct hash slot.
             *
             * @param  key the thread local object
             * @param  i the table index for key's hash code
             * @param  e the entry at table[i]
             * @return the entry associated with key, or null if no such
             */
            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;
            }
    
            /**
             * Set the value associated with key.
             *
             * @param key the thread local object
             * @param value the value to be 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();
    
                    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();
            }
    
            /**
             * Remove the entry for 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();
                        expungeStaleEntry(i);
                        return;
                    }
                }
            }
    
            /**
             * Replace a stale entry encountered during a set operation
             * with an entry for the specified key.  The value passed in
             * the value parameter is stored in the entry, whether or not
             * an entry already exists for the specified key.
             *
             * As a side effect, this method expunges all stale entries in the
             * "run" containing the stale entry.  (A run is a sequence of entries
             * between two null slots.)
             *
             * @param  key the key
             * @param  value the value to be associated with key
             * @param  staleSlot index of the first stale entry encountered while
             *         searching for key.
             */
            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).
                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
                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.
                    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
                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);
            }
    
            /**
             * Expunge a stale entry by rehashing any possibly colliding entries
             * lying between staleSlot and the next null slot.  This also expunges
             * any other stale entries encountered before the trailing null.  See
             * Knuth, Section 6.4
             *
             * @param staleSlot index of slot known to have null key
             * @return the index of the next null slot after staleSlot
             * (all between staleSlot and this slot will have been checked
             * for expunging).
             */
            private int expungeStaleEntry(int staleSlot) {
                Entry[] tab = table;
                int len = tab.length;
    
                // expunge entry at staleSlot
                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) {
                        e.value = null;
                        tab[i] = null;
                        size--;
                    } else {
                        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.
             */
            private boolean cleanSomeSlots(int i, int n) {
                boolean removed = false;
                Entry[] tab = table;
                int len = tab.length;
                do {
                    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();
    
                // Use lower threshold for doubling to avoid hysteresis
                if (size >= threshold - threshold / 4)
                    resize();
            }
    
            /**
             * Double the capacity of the table.
             */
            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);
                }
            }
        }
    附关联知识:
    ThreadLocal内存泄漏问题:https://www.cnblogs.com/xzwblog/p/7227509.html
    volatile的深入理解:https://www.jianshu.com/p/4901b2fdfce1
    线程池的详解:https://www.cnblogs.com/zzuli/p/9386463.html
    对象的强引用弱引用:https://www.cnblogs.com/yanggb/p/10386175.html
    关闭线程三种方法:https://www.cnblogs.com/liyutian/p/10196044.html
  • 相关阅读:
    P2043 质因子分解
    CODE[VS] 3164 质因数分解
    借过
    CODE[VS] 1165 字符串的展开 || P1098 字符串的展开
    CODE[VS] 1144 守望者的逃离 || P1095 守望者的逃离
    CODE[VS] 2914 吹空调
    AC日记
    AC日记
    山科日记—回文
    山科日记—编写函数myFloor和myCeil(编程题)
  • 原文地址:https://www.cnblogs.com/pu20065226/p/12228076.html
Copyright © 2011-2022 走看看