【学习笔记】深入理解ThreadLocal

开发 前端
实际上是ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,hreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置,继承自弱引用,用来保存ThreadLocal和Value之间的对应关系。

[[399381]]

一 引言

ThreadLocal的官方API解释为:

  1. * This class provides thread-local variables.  These variables differ from 
  2. * their normal counterparts in that each thread that accesses one (via its 
  3. * {@code get} or {@code set} method) has its own, independently initialized 
  4. * copy of the variable.  {@code ThreadLocal} instances are typically private 
  5. static fields in classes that wish to associate state with a thread (e.g., 
  6. * a user ID or Transaction ID). 

这个类提供线程局部变量。这些变量与正常的变量不同,每个线程访问一个(通过它的get或set方法)都有它自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,希望将状态与线程关联(例如,用户ID或事务ID)。

  1. 1、当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本, 
  2.         所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本 
  3. 2、使用ThreadLocal通常是定义为 private static ,更好是 private final static 
  4. 3、Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离 
  5. 4、ThreadLocal类封装了getMap()、Set()、Get()、Remove()4个核心方法 

从表面上来看ThreadLocal内部是封闭了一个Map数组,来实现对象的线程封闭,map的key就是当前的线程id,value就是我们要存储的对象。

实际上是ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,hreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置,继承自弱引用,用来保存ThreadLocal和Value之间的对应关系,之所以用弱引用,是为了解决线程与ThreadLocal之间的强绑定关系,会导致如果线程没有被回收,则GC便一直无法回收这部分内容。

二 源码剖析

2.1 ThreadLocal

  1. //set方法 
  2.  public void set(T value) { 
  3.      //获取当前线程 
  4.      Thread t = Thread.currentThread(); 
  5.      //实际存储的数据结构类型 
  6.      ThreadLocalMap map = getMap(t); 
  7.      //判断map是否为空,如果有就set当前对象,没有创建一个ThreadLocalMap 
  8.      //并且将其中的值放入创建对象中 
  9.      if (map != null
  10.          map.set(this, value); 
  11.      else 
  12.          createMap(t, value); 
  13.  } 
  14.  
  15.  //get方法  
  16.  public T get() { 
  17.      //获取当前线程 
  18.      Thread t = Thread.currentThread(); 
  19.      //实际存储的数据结构类型 
  20.      ThreadLocalMap map = getMap(t); 
  21.      if (map != null) { 
  22.          //传入了当前线程的ID,到底层Map Entry里面去取 
  23.          ThreadLocalMap.Entry e = map.getEntry(this); 
  24.          if (e != null) { 
  25.              @SuppressWarnings("unchecked"
  26.              T result = (T)e.value; 
  27.              return result; 
  28.          } 
  29.      } 
  30.      return setInitialValue(); 
  31.  }    
  32.  
  33.  //remove方法 
  34.   public void remove() { 
  35.       ThreadLocalMap m = getMap(Thread.currentThread()); 
  36.       if (m != null
  37.           m.remove(this);//调用ThreadLocalMap删除变量 
  38.   } 
  39.  
  40.     //ThreadLocalMap中getEntry方法 
  41.    private Entry getEntry(ThreadLocal<?> key) { 
  42.          int i = key.threadLocalHashCode & (table.length - 1); 
  43.          Entry e = table[i]; 
  44.          if (e != null && e.get() == key
  45.              return e; 
  46.          else 
  47.              return getEntryAfterMiss(key, i, e); 
  48.      }  
  49.  
  50. //getMap()方法 
  51. ThreadLocalMap getMap(Thread t) { 
  52.  //Thread中维护了一个ThreadLocalMap 
  53.      return t.threadLocals; 
  54.  } 
  55.  
  56.  //setInitialValue方法 
  57.  private T setInitialValue() { 
  58.      T value = initialValue(); 
  59.      Thread t = Thread.currentThread(); 
  60.      ThreadLocalMap map = getMap(t); 
  61.      if (map != null
  62.          map.set(this, value); 
  63.      else 
  64.          createMap(t, value); 
  65.      return value; 
  66.  } 
  67.  
  68.  //createMap()方法 
  69. void createMap(Thread t, T firstValue) { 
  70. //实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals 
  71.      t.threadLocals = new ThreadLocalMap(this, firstValue); 
  72.  } 

从上面源码中我们看到不管是 set() get() remove() 他们都是操作ThreadLocalMap这个静态内部类的,每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象

ThreadLocal.get()

  • 获取当前线程对应的ThreadLocalMap
  • 如果当前ThreadLocal对象对应的Entry还存在,并且返回对应的值
  • 如果获取到的ThreadLocalMap为null,则证明还没有初始化,就调用setInitialValue()方法

ThreadLocal.set()

  • 获取当前线程,根据当前线程获取对应的ThreadLocalMap
  • 如果对应的ThreadLocalMap不为null,则调用set方法保存对应关系
  • 如果为null,创建一个并保存k-v关系

ThreadLocal.remove()

  • 获取当前线程,根据当前线程获取对应的ThreadLocalMap
  • 如果对应的ThreadLocalMap不为null,则调用ThreadLocalMap中的remove方法,根据key.threadLocalHashCode & (len-1)获取当前下标并移除
  • 成功后调用expungeStaleEntry进行一次连续段清理

2.2 ThreadLocalMap

ThreadLocalMap是ThreadLocal的一个内部类

  1. static class ThreadLocalMap { 
  2.  
  3.          /**     
  4.          * 自定义一个Entry类,并继承自弱引用 
  5.          * 同时让ThreadLocal和储值形成key-value的关系 
  6.          * 之所以用弱引用,是为了解决线程与ThreadLocal之间的强绑定关系 
  7.          * 会导致如果线程没有被回收,则GC便一直无法回收这部分内容 
  8.          *  
  9.          */ 
  10.         static class Entry extends WeakReference<ThreadLocal<?>> { 
  11.             /** The value associated with this ThreadLocal. */ 
  12.             Object value; 
  13.  
  14.             Entry(ThreadLocal<?> k, Object v) { 
  15.                 super(k); 
  16.                 value = v; 
  17.             } 
  18.         } 
  19.  
  20.         /** 
  21.          * Entry数组的初始化大小(初始化长度16,后续每次都是2倍扩容) 
  22.          */ 
  23.         private static final int INITIAL_CAPACITY = 16; 
  24.  
  25.         /** 
  26.          * 根据需要调整大小 
  27.          * 长度必须是2的N次幂 
  28.          */ 
  29.         private Entry[] table
  30.  
  31.         /** 
  32.          * The number of entries in the table
  33.          * table中的个数 
  34.          */ 
  35.         private int size = 0; 
  36.  
  37.         /** 
  38.          * The next size value at which to resize. 
  39.          * 下一个要调整大小的大小值(扩容的阈值) 
  40.          */ 
  41.         private int threshold; // Default to 0 
  42.  
  43.         /** 
  44.          * Set the resize threshold to maintain at worst a 2/3 load factor. 
  45.          * 根据长度计算扩容阈值 
  46.          * 保持一定的负债系数 
  47.          */ 
  48.         private void setThreshold(int len) { 
  49.             threshold = len * 2 / 3; 
  50.         } 
  51.  
  52.         /** 
  53.          * Increment i modulo len 
  54.          * nextIndex:从字面意思我们可以看出来就是获取下一个索引 
  55.          * 获取下一个索引,超出长度则返回 
  56.          */ 
  57.         private static int nextIndex(int i, int len) { 
  58.             return ((i + 1 < len) ? i + 1 : 0); 
  59.         } 
  60.  
  61.         /** 
  62.          * Decrement i modulo len. 
  63.          * 返回上一个索引,如果-1为负数,返回长度-1的索引 
  64.          */ 
  65.         private static int prevIndex(int i, int len) { 
  66.             return ((i - 1 >= 0) ? i - 1 : len - 1); 
  67.         } 
  68.  
  69.         /** 
  70.          * ThreadLocalMap构造方法 
  71.          * ThreadLocalMaps是延迟构造的,因此只有在至少要放置一个节点时才创建一个 
  72.          */ 
  73.         ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { 
  74.             //内部成员数组,INITIAL_CAPACITY值为16的常量 
  75.             table = new Entry[INITIAL_CAPACITY]; 
  76.             //通过threadLocalHashCode(HashCode) & (长度-1)的位运算,确定键值对的位置 
  77.             //位运算,结果与取模相同,计算出需要存放的位置 
  78.             int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 
  79.             // 创建一个新节点保存在table当中 
  80.             table[i] = new Entry(firstKey, firstValue); 
  81.             //设置table元素为1 
  82.             size = 1; 
  83.             //根据长度计算扩容阈值 
  84.             setThreshold(INITIAL_CAPACITY); 
  85.         } 
  86.  
  87.         /** 
  88.          * 构造一个包含所有可继承ThreadLocals的新映射,只能createInheritedMap调用 
  89.          * ThreadLocal本身是线程隔离的,一般来说是不会出现数据共享和传递的行为 
  90.          */ 
  91.         private ThreadLocalMap(ThreadLocalMap parentMap) { 
  92.             Entry[] parentTable = parentMap.table
  93.             int len = parentTable.length; 
  94.             setThreshold(len); 
  95.             table = new Entry[len]; 
  96.  
  97.             for (int j = 0; j < len; j++) { 
  98.                 Entry e = parentTable[j]; 
  99.                 if (e != null) { 
  100.                     @SuppressWarnings("unchecked"
  101.                     ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); 
  102.                     if (key != null) { 
  103.                         Object value = key.childValue(e.value); 
  104.                         Entry c = new Entry(key, value); 
  105.                         int h = key.threadLocalHashCode & (len - 1); 
  106.                         while (table[h] != null
  107.                             h = nextIndex(h, len); 
  108.                         table[h] = c; 
  109.                         size++; 
  110.                     } 
  111.                 } 
  112.             } 
  113.         } 
  114.  
  115.         /** 
  116.          * ThreadLocalMap中getEntry方法 
  117.          */ 
  118.         private Entry getEntry(ThreadLocal<?> key) { 
  119.             //通过hashcode确定下标 
  120.             int i = key.threadLocalHashCode & (table.length - 1); 
  121.             Entry e = table[i]; 
  122.             //如果找到则直接返回 
  123.             if (e != null && e.get() == key
  124.                 return e; 
  125.             else 
  126.              // 找不到的话接着从i位置开始向后遍历,基于线性探测法,是有可能在i之后的位置找到的 
  127.                 return getEntryAfterMiss(key, i, e); 
  128.         } 
  129.  
  130.  
  131.         /** 
  132.          * ThreadLocalMap的set方法 
  133.          */ 
  134.         private void set(ThreadLocal<?> key, Object value) { 
  135.            //新开一个引用指向table 
  136.             Entry[] tab = table
  137.             //获取table长度 
  138.             int len = tab.length; 
  139.             ////获取索引值,threadLocalHashCode进行一个位运算(取模)得到索引i 
  140.             int i = key.threadLocalHashCode & (len-1); 
  141.             /** 
  142.             * 遍历tab如果已经存在(key)则更新值(value) 
  143.             * 如果该key已经被回收失效,则替换该失效的key 
  144.             **/ 
  145.             // 
  146.             for (Entry e = tab[i]; 
  147.                  e != null
  148.                  e = tab[i = nextIndex(i, len)]) { 
  149.                 ThreadLocal<?> k = e.get(); 
  150.  
  151.                 if (k == key) { 
  152.                     e.value = value; 
  153.                     return
  154.                 } 
  155.                 //如果 k 为null,则替换当前失效的k所在Entry节点 
  156.                 if (k == null) { 
  157.                     replaceStaleEntry(key, value, i); 
  158.                     return
  159.                 } 
  160.             } 
  161.             //如果上面没有遍历成功则创建新值 
  162.             tab[i] = new Entry(key, value); 
  163.             // table内元素size自增 
  164.             int sz = ++size
  165.             //满足条件数组扩容x2 
  166.             if (!cleanSomeSlots(i, sz) && sz >= threshold) 
  167.                 rehash(); 
  168.         } 
  169.  
  170.         /** 
  171.          * remove方法 
  172.          * 将ThreadLocal对象对应的Entry节点从table当中删除 
  173.          */ 
  174.         private void remove(ThreadLocal<?> key) { 
  175.             Entry[] tab = table
  176.             int len = tab.length; 
  177.             int i = key.threadLocalHashCode & (len-1); 
  178.             for (Entry e = tab[i]; 
  179.                  e != null
  180.                  e = tab[i = nextIndex(i, len)]) { 
  181.                 if (e.get() == key) { 
  182.                     e.clear();//将引用设置null,方便GC回收 
  183.                     expungeStaleEntry(i);//从i的位置开始连续段清理工作 
  184.                     return
  185.                 } 
  186.             } 
  187.         } 
  188.  
  189.         /** 
  190.         * ThreadLocalMap中replaceStaleEntry方法 
  191.          */ 
  192.         private void replaceStaleEntry(ThreadLocal<?> key, Object value, 
  193.                                        int staleSlot) { 
  194.             // 新建一个引用指向table 
  195.             Entry[] tab = table
  196.             //获取table的长度 
  197.             int len = tab.length; 
  198.             Entry e; 
  199.  
  200.  
  201.             // 记录当前失效的节点下标 
  202.             int slotToExpunge = staleSlot; 
  203.  
  204.            /** 
  205.              * 通过prevIndex(staleSlot, len)可以看出,由staleSlot下标向前扫描 
  206.              * 查找并记录最前位置value为null的下标 
  207.              */ 
  208.             for (int i = prevIndex(staleSlot, len); 
  209.                  (e = tab[i]) != null
  210.                  i = prevIndex(i, len)) 
  211.                 if (e.get() == null
  212.                     slotToExpunge = i; 
  213.  
  214.             // nextIndex(staleSlot, len)可以看出,这个是向后扫描 
  215.             // occurs first 
  216.             for (int i = nextIndex(staleSlot, len); 
  217.                  (e = tab[i]) != null
  218.                  i = nextIndex(i, len)) { 
  219.                  // 获取Entry节点对应的ThreadLocal对象 
  220.                 ThreadLocal<?> k = e.get(); 
  221.  
  222.                   //如果和新的key相等的话,就直接赋值给value,替换i和staleSlot的下标 
  223.                 if (k == key) { 
  224.                     e.value = value; 
  225.  
  226.                     tab[i] = tab[staleSlot]; 
  227.                     tab[staleSlot] = e; 
  228.  
  229.                     // 如果之前的元素存在,则开始调用cleanSomeSlots清理 
  230.                     if (slotToExpunge == staleSlot) 
  231.                         slotToExpunge = i; 
  232.                      /** 
  233.                      *在调用cleanSomeSlots()    清理之前,会调用 
  234.                      *expungeStaleEntry()从slotToExpunge到table下标所在为 
  235.                      *null的连续段进行一次清理,返回值就是tablenull的下标 
  236.                      *然后以该下标 len进行一次启发式清理 
  237.                      * 最终里面的方法实际上还是调用了expungeStaleEntry 
  238.                       * 可以看出expungeStaleEntry方法是ThreadLocal核心的清理函数 
  239.                      */ 
  240.                     cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); 
  241.                     return
  242.                 } 
  243.  
  244.                 // If we didn't find stale entry on backward scan, the 
  245.                 // first stale entry seen while scanning for key is the 
  246.                 // first still present in the run. 
  247.                 if (k == null && slotToExpunge == staleSlot) 
  248.                     slotToExpunge = i; 
  249.             } 
  250.  
  251.             // 如果在table中没有找到这个key,则直接在当前位置new Entry(key, value) 
  252.             tab[staleSlot].value = null
  253.             tab[staleSlot] = new Entry(key, value); 
  254.  
  255.             // 如果有其他过时的节点正在运行,会将它们进行清除,slotToExpunge会被重新赋值 
  256.             if (slotToExpunge != staleSlot) 
  257.                 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); 
  258.         } 
  259.  
  260.         /** 
  261.          * expungeStaleEntry() 启发式地清理被回收的Entry 
  262.          * 有两个地方调用到这个方法 
  263.          * 1、set方法,在判断是否需要resize之前,会清理并rehash一遍 
  264.          * 2、替换失效的节点时候,也会进行一次清理 
  265.         */ 
  266.           private boolean cleanSomeSlots(int i, int n) { 
  267.             boolean removed = false
  268.             Entry[] tab = table
  269.             int len = tab.length; 
  270.             do { 
  271.                 i = nextIndex(i, len); 
  272.                 Entry e = tab[i]; 
  273.                 //判断如果Entry对象不为空 
  274.                 if (e != null && e.get() == null) { 
  275.                     n = len; 
  276.                     removed = true
  277.                     //调用该方法进行回收, 
  278.                     //对 i 开始到table所在下标为null的范围内进行一次清理和rehash 
  279.                     i = expungeStaleEntry(i); 
  280.                 } 
  281.             } while ( (n >>>= 1) != 0); 
  282.             return removed; 
  283.         }   
  284.  
  285.         private int expungeStaleEntry(int staleSlot) { 
  286.             Entry[] tab = table
  287.             int len = tab.length; 
  288.  
  289.             // expunge entry at staleSlot 
  290.             tab[staleSlot].value = null
  291.             tab[staleSlot] = null
  292.             size--; 
  293.  
  294.             // Rehash until we encounter null 
  295.             Entry e; 
  296.             int i; 
  297.             for (i = nextIndex(staleSlot, len); 
  298.                  (e = tab[i]) != null
  299.                  i = nextIndex(i, len)) { 
  300.                 ThreadLocal<?> k = e.get(); 
  301.                 if (k == null) { 
  302.                     e.value = null
  303.                     tab[i] = null
  304.                     size--; 
  305.                 } else { 
  306.                     int h = k.threadLocalHashCode & (len - 1); 
  307.                     if (h != i) { 
  308.                         tab[i] = null
  309.                         while (tab[h] != null
  310.                             h = nextIndex(h, len); 
  311.                         tab[h] = e; 
  312.                     } 
  313.                 } 
  314.             } 
  315.             return i; 
  316.         } 
  317.  
  318.  
  319.  
  320.         /** 
  321.          * Re-pack and/or re-size the tableFirst scan the entire 
  322.          * table removing stale entries. If this doesn't sufficiently 
  323.          * shrink the size of the tabledouble the table size
  324.          */ 
  325.         private void rehash() { 
  326.             expungeStaleEntries(); 
  327.  
  328.             // Use lower threshold for doubling to avoid hysteresis 
  329.             if (size >= threshold - threshold / 4) 
  330.                 resize(); 
  331.         } 
  332.  
  333.         /** 
  334.          * 对table进行扩容,因为要保证table的长度是2的幂,所以扩容就扩大2倍 
  335.          */ 
  336.         private void resize() { 
  337.         //获取旧table的长度 
  338.             Entry[] oldTab = table
  339.             int oldLen = oldTab.length; 
  340.             int newLen = oldLen * 2; 
  341.             //创建一个长度为旧长度2倍的Entry数组 
  342.             Entry[] newTab = new Entry[newLen]; 
  343.             //记录插入的有效Entry节点数 
  344.             int count = 0; 
  345.  
  346.              /** 
  347.              * 从下标0开始,逐个向后遍历插入到新的table当中 
  348.              * 通过hashcode & len - 1计算下标,如果该位置已经有Entry数组,则通过线性探测向后探测插入 
  349.              */ 
  350.             for (int j = 0; j < oldLen; ++j) { 
  351.                 Entry e = oldTab[j]; 
  352.                 if (e != null) { 
  353.                     ThreadLocal<?> k = e.get(); 
  354.                     if (k == null) {//如遇到key已经为null,则value设置null,方便GC回收 
  355.                         e.value = null; // Help the GC 
  356.                     } else { 
  357.                         int h = k.threadLocalHashCode & (newLen - 1); 
  358.                         while (newTab[h] != null
  359.                             h = nextIndex(h, newLen); 
  360.                         newTab[h] = e; 
  361.                         count++; 
  362.                     } 
  363.                 } 
  364.             } 
  365.             // 重新设置扩容的阈值 
  366.             setThreshold(newLen); 
  367.             // 更新size 
  368.             size = count
  369.              // 指向新的Entry数组 
  370.             table = newTab; 
  371.         } 
  372.  
  373.  
  374.     } 

ThreadLocalMap.set()

  • key.threadLocalHashCode & (len-1),将threadLocalHashCode进行一个位运算(取模)得到索引 " i " ,也就是在table中的下标
  • for循环遍历,如果Entry中的key和我们的需要操作的ThreadLocal的相等,这直接赋值替换
  • 如果拿到的key为null ,则调用replaceStaleEntry()进行替换
  • 如果上面的条件都没有成功满足,直接在计算的下标中创建新值
  • 在进行一次清理之后,调用rehash()下的resize()进行扩容

ThreadLocalMap.expungeStaleEntry()

  • 这是 ThreadLocal 中一个核心的清理方法
  • 为什么需要清理?
  • 在我们 Entry 中,如果有很多节点是已经过时或者回收了,但是在table数组中继续存在,会导致资源浪费
  • 我们在清理节点的同时,也会将后面的Entry节点,重新排序,调整Entry大小,这样我们在取值(get())的时候,可以快速定位资源,加快我们的程序的获取效率

ThreadLocalMap.remove()

  • 我们在使用remove节点的时候,会使用线性探测的方式,找到当前的key
  • 如果当前key一致,调用clear()将引用指向null
  • 从"i"开始的位置进行一次连续段清理

三 案例

目录结构:

在这里插入图片描述

HttpFilter.java

  1. package com.lyy.threadlocal.config; 
  2.  
  3. import lombok.extern.slf4j.Slf4j; 
  4.  
  5. import javax.servlet.*; 
  6. import javax.servlet.http.HttpServletRequest; 
  7. import java.io.IOException; 
  8.  
  9. @Slf4j 
  10. public class HttpFilter implements Filter { 
  11.  
  12. //初始化需要做的事情 
  13.     @Override 
  14.     public void init(FilterConfig filterConfig) throws ServletException { 
  15.  
  16.     } 
  17.  
  18.     //核心操作在这个里面 
  19.     @Override 
  20.     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 
  21.         HttpServletRequest request = (HttpServletRequest)servletRequest; 
  22. //        request.getSession().getAttribute("user"); 
  23.         System.out.println("do filter:"+Thread.currentThread().getId()+":"+request.getServletPath()); 
  24.         RequestHolder.add(Thread.currentThread().getId()); 
  25.         //让这个请求完,,同时做下一步处理 
  26.         filterChain.doFilter(servletRequest,servletResponse); 
  27.  
  28.  
  29.     } 
  30.  
  31.     //不再使用的时候做的事情 
  32.     @Override 
  33.     public void destroy() { 
  34.  
  35.     } 

HttpInterceptor.java

  1. package com.lyy.threadlocal.config; 
  2.  
  3. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 
  4.  
  5. import javax.servlet.http.HttpServletRequest; 
  6. import javax.servlet.http.HttpServletResponse; 
  7.  
  8. public class HttpInterceptor extends HandlerInterceptorAdapter { 
  9.  
  10.     //接口处理之前 
  11.     @Override 
  12.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 
  13.         System.out.println("preHandle:"); 
  14.         return true
  15.     } 
  16.  
  17.     //接口处理之后 
  18.     @Override 
  19.     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 
  20.         RequestHolder.remove(); 
  21.        System.out.println("afterCompletion"); 
  22.  
  23.         return
  24.     } 

RequestHolder.java

  1. package com.lyy.threadlocal.config; 
  2.  
  3. public class RequestHolder { 
  4.  
  5.     private final static ThreadLocal<Long> requestHolder = new ThreadLocal<>();// 
  6.  
  7.     //提供方法传递数据 
  8.     public static void add(Long id){ 
  9.         requestHolder.set(id); 
  10.  
  11.     } 
  12.  
  13.     public static Long getId(){ 
  14.         //传入了当前线程的ID,到底层Map里面去取 
  15.         return requestHolder.get(); 
  16.     } 
  17.  
  18.     //移除变量信息,否则会造成逸出,导致内容永远不会释放掉 
  19.     public static void remove(){ 
  20.         requestHolder.remove(); 
  21.     } 

ThreadLocalController.java

  1. package com.lyy.threadlocal.controller; 
  2.  
  3. import com.lyy.threadlocal.config.RequestHolder; 
  4. import org.springframework.stereotype.Controller; 
  5. import org.springframework.web.bind.annotation.RequestMapping; 
  6. import org.springframework.web.bind.annotation.ResponseBody; 
  7.  
  8. @Controller 
  9. @RequestMapping("/thredLocal"
  10. public class ThreadLocalController { 
  11.  
  12.     @RequestMapping("test"
  13.     @ResponseBody 
  14.     public Long test(){ 
  15.         return RequestHolder.getId(); 
  16.     } 
  17.  

ThreadlocalDemoApplication.java

  1. package com.lyy.threadlocal; 
  2.  
  3. import com.lyy.threadlocal.config.HttpFilter; 
  4. import com.lyy.threadlocal.config.HttpInterceptor; 
  5. import org.springframework.boot.SpringApplication; 
  6. import org.springframework.boot.autoconfigure.SpringBootApplication; 
  7. import org.springframework.boot.web.servlet.FilterRegistrationBean; 
  8. import org.springframework.context.annotation.Bean; 
  9. import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 
  10. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 
  11.  
  12. @SpringBootApplication 
  13. public class ThreadlocalDemoApplication extends WebMvcConfigurerAdapter { 
  14.  
  15.     public static void main(String[] args) { 
  16.         SpringApplication.run(ThreadlocalDemoApplication.class, args); 
  17.     } 
  18.  
  19.     @Bean 
  20.     public FilterRegistrationBean httpFilter(){ 
  21.         FilterRegistrationBean registrationBean = new FilterRegistrationBean(); 
  22.         registrationBean.setFilter(new HttpFilter()); 
  23.         registrationBean.addUrlPatterns("/thredLocal/*"); 
  24.  
  25.  
  26.         return registrationBean; 
  27.     } 
  28.  
  29.  
  30.     @Override 
  31.     public void addInterceptors(InterceptorRegistry registry) { 
  32.         registry.addInterceptor(new HttpInterceptor()).addPathPatterns("/**"); 
  33.     } 
  34.  

输入:http://localhost:8080/thredLocal/test

后台打印:

  1. do filter:35:/thredLocal/test preHandle: 
  2. afterCompletion 

四 总结

1、ThreadLocal是通过每个线程单独一份存储空间,每个ThreadLocal只能保存一个变量副本。

2、相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值,很好的实现了线程封闭。

3、每次使用完ThreadLocal,都调用它的remove()方法,清除数据,避免内存泄漏的风险

4、通过上面的源码分析,我们也可以看到大神在写代码的时候会考虑到整体实现的方方面面,一些逻辑上的处理是真严谨的,我们在看源代码的时候不能只是做了解,也要看到别人实现功能后面的目的。

源码地址:https://github.com/839022478/other/tree/master/threadlocal_demo

 

责任编辑:姜华 来源: 牧小农
相关推荐

2020-12-11 07:32:45

编程ThreadLocalJava

2016-08-31 15:50:50

PythonThreadLocal变量

2016-11-07 21:59:52

threadpython

2016-08-31 15:41:19

PythonThreadLoca变量

2012-11-14 09:57:46

JavaJava虚拟机JVM

2016-12-08 15:36:59

HashMap数据结构hash函数

2010-06-01 15:25:27

JavaCLASSPATH

2020-07-21 08:26:08

SpringSecurity过滤器

2015-06-24 10:18:26

2020-09-23 10:00:26

Redis数据库命令

2017-01-10 08:48:21

2024-02-21 21:14:20

编程语言开发Golang

2019-06-25 10:32:19

UDP编程通信

2017-08-15 13:05:58

Serverless架构开发运维

2023-10-19 11:12:15

Netty代码

2021-02-17 11:25:33

前端JavaScriptthis

2009-09-25 09:14:35

Hibernate日志

2013-09-22 14:57:19

AtWood

2023-09-26 08:01:46

消费者TopicRocketMQ

2020-11-13 16:40:05

RocketMQ延迟消息架构
点赞
收藏

51CTO技术栈公众号