【学习笔记】深入理解ThreadLocal

实际上是ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,hreadLocal确定了一个

【学习笔记】深入理解ThreadLocal

一 引言

ThreadLocal的官方API解释为:

  1. *Thisclassprovidesthread-localvariables.Thesevariablesdifferfrom
  2. *theirnormalcounterpartsinthateachthreadthataccessesoneA } F L u / Q 9 d(viaits
  3. *{@codeget}or{I u m@codeset}method)hasitsown,indepeny q y 9 Odentlyinitialized
  4. *copyofthevariable.{@c( p 3 V ~ 1 : v KodeThreadLocal}instancesaretypicallyp% o Wrivate
  5. *staticfieldsinclassesthatwishtoassociatestatewithathread(e.g.,
  6. *auserIDorTransactionID).

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

  1. 1、当使用ThreadLocal维护变量时,Thr| T O g J c I ^eadLocaU L z 1 R Ql为每个使用该变量的线程提供独立的变量副本,
  2. 所以J p 1 U 1 D每一个线程都可以独立地改变自己的副本E | ~ @ 6 @ | F 4,而不会影响其它线程所对应的副本
  3. 2、使用ThreadLocal通常是定义为privatestaticy D b , J V P更好是privatefinalstatic
  4. 3、Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离
  5. 4、ThreadLocal类封装了getMap()、Set()、Get()、Remove()4个核心方法

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

实际上是ThreadLocal的静态内部类T\ T G l Ehrea+ P D : I P 0 & ;dLocalMap为每个Thread都维护了一个数组table,hreadLocal确定了一个数组下标,而这个下标就是value3 O % 2 O b _ @ n存储的对应位置,继承自弱引用,用来保存ThreadLocal和Value之间v d B ^ n的对应关系,之所以用弱引用,是为了解决线程与ThreadLo\ Q o ] | L Q \ `cal之间的强绑定关系,会导致如果线程没有被回收,则GC便一直无法回收这部分内容。

二 源码剖析

2.1 ThreadLocal

  1. //set方法
  2. publig - y &cvoidset(Tvalue){
  3. //获取当前线程
  4. Threadt=Thread.currentThrx ( ; ` l Pead();
  5. //实际存储的数A H k ~ T : H m据结构类型
  6. ThreadLocalMapmap=getMap(t);
  7. //判断map是否为空,如果有就set当前对象,没N + % _ C x I j V有创建一个Threa} 0 i ; 3 \ ,dLocalMap
  8. //并且将其中的值放入创建对象中
  9. if(map!=null)
  10. map.set(this,value);
  11. else
  12. createMap(t,value);
  13. }
  14. //g3 _ 9 ` bet方法
  15. publicTget(){
  16. //获取当前线程
  17. Threadt=Thread.currentThread();
  18. //实际存储的数据结构类型
  19. ThreadLocalMapmap=getMap(t3 q 6 e 4 U);
  20. if(map!=nulls c # e T # D H){L s ^ # v
  21. //传入了当前线程的ID,到底层Ma\ k = s l } \ [pEnt; Z r \ 7 Hryz 8 i d I里面去取
  22. ThreadLocalMap.Enf { [ 7 P Ftrye=map.getEntry(this);
  23. if(e!=& C ) }null){
  24. @/ K / z y o ) JS] 9 ~ 0 A X n + 7uppressWarnings("unchecked")
  25. Tresult=(T)e.value;
  26. retur9 X o Rnresult;
  27. }
  28. }
  29. retb p , / )urnsetInitialValue(! f 6);
  30. }
  31. //remove方法
  32. pu| N 6 u m N ? v 3blicvoidremove(){
  33. ThreadLocalMapm=getMap(Thread.currentThread());
  34. if(m!=null)
  35. m.remove(this);//调用Thred L GadLocalMap删除变量
  36. }
  37. //ThreadLocalMap中getEntry方法
  38. priv. 3 E 8 gateEntrygetEntry(ThU f +readLocal<?>key){
  39. inti=key.threadLocalHashCode&am? | 4 x 3 op;(table.length-1F J { ] t + ^ r $);
  40. Entrye=table[i];
  41. if(e!=null&&e.get()==key)d ` n x # :
  42. returne;
  43. else
  44. returngetEntryAr d y OfterMiss(key,i,e);
  45. }
  46. //getMap()方法
  47. ThreadLocalMaX j D -pgetMap(Threadt){
  48. //Thread中维护了一~ \ d W d c E %个ThreadLocalMap
  49. returnt.threadLocv | / I = ; b @ [als;
  50. }
  51. //setInitialValue方法U Q M * Y
  52. privateTsetInitialValue(){
  53. Tvalue=initialValue();
  54. Threadt=Thread.currentThread(: . Z ( O);
  55. ThreadLi o E 2 $ocalMapmap=getMap(t);
  56. if(map!=null)
  57. m1 8 ; W r S 8ap.set(this,value);
  58. else
  59. createMap(t,value);
  60. returnvalue;
  61. }
  62. //createMap()方法
  63. voidcreateMap(Threadt,TfirB V , *stValue){
  64. //实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threa# { G ^ J ^ S Y EdLo6 9 Vcals
  65. t.threadLocals=newThreadLocalMa^ u # I p ? $ 4p(this,firstValue);
  66. }

从上面源码中我们看到不管是 set()c , r $ q get() remove() 他们都是操作ThreadLocalMap这个静态内部类的,每一个新的线程Thread都会实例化一个ThreadLocalMd U A : ] \ _ yap并赋值给成员变量tf 7 DhreadLocals,使用时若已经存在threadLocals则直接使用已经存在的对. 8 A

ThreadLocal.get(n ? `)

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

ThreadLocal.set()

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

ThreadLocal.g # iremove()

  • 获取当前线程,根据当前3 R f线程获取对应/ e Q !的ThreadLoc^ y 6 Q 6 KalMap
  • K o E果对应的ThreadLocalMap不为null,则调用ThreadI I & W z 4 SLocalMap中的remove方法,根据key.threadLocalHashCode & (len-1)获取当前下标并移除
  • 成功后调用expungek 6 7 \ C \ m _ hStaleEntry进行一次连续段清理

【学习笔记】深入理解ThreadLocal

2.2 ThreadLocalMap

ThreadLocalMap是ThreadLocal的一8 t i个内部类

  1. staticclassThreadLocalMap{
  2. /**
  3. *自定义一个Entry类,并继承自弱引用
  4. *同时让ThreadLocal和储值形成key. G 7 E-value的关系
  5. *之所以用弱引用,是为了解决线L d } [ * ; D S程与ThreadLocal之间的强9 u g 6 1 [绑定关系
  6. *会导致如果线程没有被回收,则GC便一直无法回收这部分内容
  7. *
  8. */
  9. staticclassEntryextendsWeakRe_ ) & j ? q 2 ] Aference<Thre{ f - * ,adLocal<?>>{
  10. /**Thevalueassocin a G @atedwiththisThreadLocal.*/
  11. Objectvalue;
  12. Entry(ThreadLocal<?>5 $ A R;k,Objectv){
  13. super(k);
  14. value=v;
  15. }
  16. }
  17. /**
  18. *l - A + ! P A #Entry数组的初始化大小(初始化长度16,后续; j q + ` w每次都是2倍扩容)
  19. *F R Y/
  20. privatestaticfinalintINITIAL_CAPAC$ O 9 F [ [ M y nITY=16;
  21. /**
  22. *根据需要调整大小
  23. *长度必须是2的N次幂
  24. */
  25. privateEntry[]table;
  26. /**
  27. *Thenumberofentriesinthetable.
  28. *table中的个数
  29. */
  30. privateintsize=0;
  31. /**
  32. *Thenextsizevalueatwhichtoresize.
  33. *下一个要调整大小的大小值(扩容的阈值)
  34. */
  35. privateinttN N o w T m , L -hreshot e h \ Z U E u ]ld;//Defaultto0
  36. /**
  37. *SettheresizethresholdtomainE i n Otain/ W ^ w 1 Ratworsta2/3loadfactor.
  38. *根据长度计算扩容阈值
  39. *保持一定的负债系数
  40. */
  41. privatevoidsetThreshold(intlen){
  42. threshh B f - xold=len*2/3;
  43. }
  44. /**
  45. *IncremZ d i b + 6 yentimodulolen
  46. *nextIndex:从字面意思我们可以看出来就是获取下一个索引
  47. *获取下一个X d V 8 1 6 3 8索引,超出长度则返回
  48. */
  49. privatestaticintnextIndex(inti,intlen){
  50. return((i+1<len)?i+1:0);
  51. }
  52. /**
  53. *Decrementimodulolen.
  54. *返回上一个索引,如果-1为负数,返回长度-1的索引
  55. */
  56. privatestaticintp$ \ L M \ 1revIndex(i* B L i 8 1 9 tnti,intlen){
  57. return((i-1>=0)?i@ h K F & J G-1:len-1);
  58. }
  59. /**
  60. *ThreadLocalMap构造方法
  61. *ThreadLocalMaps是延迟构造的,因此只有在至少要放置一个节点时才创建一个
  62. */
  63. ThreadLocalMap(Th6 / h $ + m 7readLocal<?>firstKb { \ % E n ]ey,ObjectfirstValue){
  64. //内部成员数组,INITIAL_CAPACITY值为16的常量
  65. table=newEntry[INITIAL_CAPACITY];
  66. //通过threadLocalHashCode(HashCode)&(长度-1)的位运算,确定键值对的位置
  67. //位运算,结果与取模相同,计算出需要存放的位置
  68. inti=firstKey.threadLocalHashCode&(INITIAL_CAPACITY-1);
  69. //创建一个新节点e | ) q } _ H U保存在table当中
  70. table, O y[i]=newEntry(firstKey,firstValue);
  71. //设置table元素为1
  72. size=1;
  73. //根据长度计算扩容阈值
  74. setThreshold(INITIAL_CAPACITu y : M DY);
  75. }
  76. /**
  77. *构造一个包含所有可继承ThreadL8 ` K \ x H f M :ocals的新映射,只能createInheritedMap调用
  78. *ThreadLoc6 X b Gal本身是7 J 8 Z j !线程隔离的,一般来说是不会出现数据共享和传递的行为
  79. */
  80. privateThreadLocalMap(ThreadLocalMaB { \ r LpparentMap){
  81. Entry[]parent7 R ? p -Table=parentMap.ta5 : m Oble;
  82. intlen=parentTable.length;
  83. setThreshold(len);
  84. table=newEntry[len];
  85. for{ m Z(intj=0;j&ly . X % tt;len;j++){
  86. Entrye=parentTable[j];
  87. if(e!=null){
  88. @SuppressWarnings("unchecked")
  89. ThreadLocal<Object>key=(ThreadLocal<Object>)e.get();
  90. if(key!=null){
  91. ObjectvR 7 7 nalue=key.childValue(e.value);
  92. Entryc=newEntry(key,value);
  93. inth=key.threadLocalHashCode&amy R 3 Qp;(len-1);
  94. while(table[h]!=null)
  95. h=nextIndex(h,len);
  96. table[h]=c;
  97. size+z O :+;
  98. }
  99. }
  100. }
  101. }
  102. /**
  103. *ThreadLocalMaK * , Up中getEntry方法
  104. */
  105. privateEntrygetEntry(ThreadLocal<?>key){
  106. //通过hashcode确定下标
  107. inti=key.threadLocalHashCode&(table.lengW r O 8 Hth-1);
  108. Entf * v Q /rye=table[i];
  109. //如果找到则直接返回
  110. if(e!=null&&am| y C d ~p;e.get()==key)
  111. returne;
  112. else
  113. //找不到的话接着从i位置开始向后遍历,基于线性探L % j v B K测法,是有可能在i之后的位置$ \ @ k 3 3 y找到的
  114. returngetEnE T t ;tryAfterMiss(key,i,e);
  115. }
  116. /**
  117. *ThreadLocalMap的set方法
  118. */
  119. privatevoidset(ThreadLocal<?>key,Objectvalue){
  120. //新开一个引用指向table
  121. Entry[]tab=table;
  122. //获取table长度
  123. intlen=tab.length;
  124. ////获取索引值,threadLocal* k 0 0 { ]HashCode进行一个位运算(取模)得到索引i
  125. intiD ? c d \ U _ |=kp - X [ } G Oey.threadLoC / YcalHashCode&(len-1);
  126. /**
  127. *遍历tab如果已经存在(key)则更新值(value)
  128. *如果该key已经被回收失效,则替换该L ? z w失效的key
  129. **/
  130. /I , O g C p/
  131. for(Entrye=tab[i];
  132. e!=null;
  133. e=tab[i=nextIndex(i,len)]){
  134. ThreadLocal<?>k=e.get();
  135. if(k==key){
  136. e.value=value;
  137. return;
  138. }
  139. //如果k为nullf = . j a [则替换当前失效的k所在Entry节点
  140. ifO V 9 J 2 k(k==null){
  141. replaceStaleEntry(key,value,i);
  142. return;
  143. }
  144. }
  145. //如果上面没有遍历成功则创建新值H 7 w
  146. ta` s o i K 3 ~ +b[i]=newEntry(key,valW 0 + A o *ue);
  147. //tab] % r ,le内元素3 _ x l # B 9 Dsize自增
  148. intsz=g D ) ^ ,++size;
  149. //满足条件数组扩容x2
  150. if(!cleanSomeSlots(i,sz)V W ; H q ]&&sz>=threshold)
  151. rehash();
  152. }
  153. /**
  154. *8 b , K , Nremove方法
  155. *将ThreadLocal对象对应的Entryp x A @ d H s 0 q节点从table当中删除
  156. */
  157. privatevoidremove(ThreadLocal<8 } * # u & N V ^;?>key){
  158. Entry[]tab=table;
  159. intlen=tab.length;
  160. intid ; R X G=key.tv D z + G 7 ] ^ khreadLocalHashCode&(len-1);
  161. for(Entrye=tab[i];
  162. e!=null;
  163. e=tab[i=nextIndex(i,len)]){
  164. if(e.get()==key){
  165. e.clear();//将引用设置null,方便GC回收
  166. expungeStaleEntry(i);//从i的位置开始连续段_ S b S { #清理工作
  167. return;
  168. }
  169. }
  170. }
  171. /**
  172. *ThreadLocalMap中re3 M xplaceStaleEntry方法
  173. */
  174. pri$ g ` D Q f A )v3 ^ b 2 t catevoidreplaceStaleEntry(ThreadLt [ N # docal<?>key,Objectvalue,
  175. intstaleSlot){
  176. //新建一个引用指向table
  177. Entry[]tab=table;
  178. //获取tabT ! x 9 [ N J N \le的长度
  179. intlen=taH = M Wb.length;
  180. Entrye;
  181. //记录当前失效的节点下标
  182. intslotToExpunge=staleSlot;H , ! Z s
  183. /**
  184. *通过prevIndex(staleSlot,len)可以看出,由staleSlot下标向前扫描
  185. *查找并记# [ M % O录最前位置- I x 1 y e 8 q pvalue为null的下标
  186. */
  187. for(inti=prevIndq l ` s Xex(staleSlot,len);
  188. (e=tab[i])!=null;
  189. i=prevIndex(i,len))
  190. if(e.get()==no ? = ~ zull)
  191. slotToExpunge=i;
  192. //nextIndex(staleSlot,len)可以看出,这个是向后扫描
  193. //occursfirstR e / g 8 @
  194. for(inti=nextIndex(staleSlot,len);
  195. (e=tab[i])!=null;
  196. i=nextIndex(i,len)){
  197. //获取EnU ? @try节点对应的ThreadLocal对象
  198. ThreadLocal<?>k=e.get();
  199. //如果和新的key相等的[ t - c H u话,就直接赋值给value,替换i和staleSlot的下标
  200. if(k==key){
  201. e.value=value;
  202. tab[i]=tab[staleSlot];
  203. tab[staleSlot]=e;
  204. //如果之前的元素存在,则开始调用cleanSomeSlots清理
  205. if(slotToExpunge==staleSlot)
  206. slotT@ j T FoE% l E 5 I C W .xpunge=i;
  207. /**
  208. *在调用cleanSomeSlots()U 2 Z f `清理之前,会调用
  209. *expungeStaleEntry()从slotToExpunge到table下标所在为
  210. *null的连^ r : G P P 2 [续段进行一次清理,返回值就是tablenullw f + : f W下标
  211. *然后以该下标len进行一次启发式清理
  212. *最终里面的方法实际上还是调用了exR z % MpungeStaleEntry
  213. *可以看出expungeStalM ` ? & ) Z j 7 ueEntry方法是Thrb B TeadLocal核心的清理函数
  214. */
  215. cleanSomeSlT Z A a . o )otO q Xs(expungeStald p & a C ( CeEntry| * 2 Y H(slotToEx! 2 ? spunge),len);
  216. return;
  217. }
  218. //IC B 3 7 G b _ afw% ? G 2 4 = Q /edidn'tfindstaleentryonbackwardscan,the
  219. //firststaleentryseenwhilescanningforkeyisthe
  220. //firststillpresentin) R D q D D 3therun.
  221. if(k==null&am` w \ V Qp;&slotT2 R 4 ( AoExpunge==staleSlot)
  222. slotToExpung5 s 0 Z G N m ` Ie=i;
  223. }
  224. //如果在table中没有找到这个key,则直接在当前T D W 2 7 ( S V位置newEntry(key,value)
  225. tab[staleSlot].value=K v z N C C H x snul= - N 2 M F \ Tl;
  226. tab[staleSlot]=newEntry(key,value);
  227. //如果有其他过时的节点正在运行,会将它们进行清除,slotToExpunge会被重新赋值
  228. if(slotToExpunge!=staleSlot)
  229. cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);
  230. }
  231. /**
  232. *expungeStaleEntry()启发式地清理被回收的Entry
  233. *有两个地方调用到这个方法
  234. *1、set方法,在判断是否需要resize之前,会清理并rehash一遍
  235. *2、替换失效的节点时候,也会进行一次清理
  236. */
  237. privatebooleancleanSomeSlots(inti,inY . m N A @ Vtn){
  238. booleanremoved=false;
  239. EntryA E : 5 5[]tab=table;
  240. intlen=tab.length;
  241. do{
  242. i=nextIndex(i` q R 5 % I,len);
  243. Entrye=tab[i];
  244. //判断如果Entry对象不为空
  245. if(e!=null&&e.get()==null){
  246. n=len;
  247. removed=true;
  248. //调用该方法进行回收,
  249. //对i开始到table所在下标为null的范围内进行一次清理和rehash
  250. i=expungeStaleEntry(i);
  251. }
  252. }while((n>>>=1)!=0);
  253. returnremoved;C 9 T
  254. }
  255. privateintexpungeSv q 8 ^ @ m E A otaleEntry(intstaleSlot){
  256. Enh e ^ k / 7try[]tab=table;
  257. intlen=tab.length;
  258. //expungeentB T % { r gryatstales f = oSlot
  259. tab[staleSlot].value=null! ! - u - H r \ /;
  260. tab[staleSlot]=null;
  261. size--;
  262. //ReE = @ i 4hashuntilweey D 4 T u ^ H ~ 0ncounternull
  263. Entrye;
  264. inti;
  265. for(i=! r F w \ F C 6 vnextIndex(staleSlot,len);
  266. (e=tab[i])!=null;
  267. i=nextIndex(i,len)){
  268. ThreadLocal<?>k=e.get();
  269. if(k==null){
  270. e.value=null;5 _ | k ~ #
  271. tab[i]=nu& g h @ R @ vll;
  272. size--;
  273. }else{
  274. inth=k.t# { w I 7 k r yhreadLocalH1 T : s I 1ashCode&(len-1);
  275. if(h!=i){
  276. tab[i]=null;
  277. while(tab[h]!=null)
  278. h=nextIndexi 1 j G 9 6 M 1(h,len);
  279. tab[h]=e;
  280. }
  281. }
  282. }
  283. returni;
  284. }
  285. /**
  286. *Re-packand/orre-sizethA $ 9 U Vetable.Firstscantheen^ f atire
  287. *tableremovingstaleentries.Ifthisdoe[ g 2 r V E z asn'tsufficiently
  288. *shrinkthesizeofthetable,doublethetab9 x Hlesize.
  289. */
  290. pi \ 6 !rivatevoidrehash(){
  291. expungeStaleEntries();
  292. //Ub 0 O j f ; 7selowerthresholdfordoublingtoavoidhysteresis
  293. if(size>=threshF & - 9old-threshold/4)
  294. resize();
  295. }
  296. /**
  297. *2 K h ` k ~ u D [table进行扩容,因为要保证table的长度是2的幂,所以扩容就扩大2倍
  298. */
  299. privatevoidresize()E \ = y #{
  300. //获取旧table的长度
  301. Entri 9 Q + a : C + @y[]oldTab=table;
  302. intoldLen=oldTa- 1 + 2 +b.length;
  303. intnewLen=oldLE q S [ Y 7 p $en*2;
  304. //创建一个长度为旧长度2倍的Entry数组
  305. EntrA u V n n 2 R &y[]newTab=newEntry[newLen];
  306. //记录插入的有效Entry节点数
  307. intcount=0;
  308. /**
  309. *从下标0开始,逐个向后遍历插入到新的table当中
  310. *通过hashcode&len-1计算下标,如果该位置已经有Entry数组,则通过线性探测向后探测插入
  311. */
  312. for(intj=0;j<oldLen;++j){
  313. Entrye=oldTab[j];
  314. if(e!=null){
  315. ThreadLocal<?>k=e.get();
  316. if(k==null){//如遇到key已经为null,则value设置null,方便GC回收
  317. e.value=null;//HelptheGCy s # E @
  318. }else{
  319. inth=k.threadLocalHashCode&(newLen-1);
  320. while(newTab[h]!=nu3 r -ll)
  321. h=nextIndex(h,newLen);
  322. newTay U u Lb[h]=e;
  323. count++;
  324. }
  325. }
  326. }
  327. //重新设置扩容的阈值
  328. setThreshold(newLen);
  329. //更新size
  330. size=count;
  331. //指向新的Entry数组
  332. table=newTab;
  333. }
  334. }

ThreadLocalMap.set()

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

ThreadLocalMap.expungeStaleEntry()

  • v $ a W ( r s是 ThreadLocal 中一个核心的清理方法
  • 为什么需要清理?
  • 在我们 En_ m { b Ctry 中,如果[ U _ m % G有很多节点是已经过时或者回收了,但是在table数组中继续存在,会导致资源浪费
  • 我们v ; C )在清理节点的同时* . T t ( _,也会将后面的Entry节点,重O V k ]新排序,调整y d B Z 5 p jEh ( Q Y k _ Mntry大小,这样我们在取值(get())的时候,可以快速定位资源,3 t 0 0 O u { 9 7加快我们的程序的获取效率

ThreadLocalMap.remove()

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

三 案例

目录结构:m H `

【学习笔记】深入理解ThreadLocal

在这里插入图片描述

HttpFilter.java

  1. packagecom.lyy.threadlocal.config;
  2. importlombok.exteU _ g 5 4 V ^ ! irn.slf4j.Slf4j;
  3. importjavax.servlet.*;
  4. importjavax.servlet.http.Httpc 8 MServletRequest;
  5. imp* A M h [ hortjava.io.If ? % _ N C z : kOException;
  6. @Slf4j
  7. publicclassHttpFilterimplementsFilter{
  8. //初始化需要做的事情
  9. @Override
  10. puH c \ E g K 5blicvoidinit(FilterConfigfilterConfig)throwsServletException{
  11. }
  12. //核心操作在这个里面
  13. @Override
  14. publicvoiddoFilted $ t B = tr(ServletRequestservletRequest,ServletResponseservletResponse,! a H P 7 F L - hFilterChainfilterChain)throwsIOExceptionb A B,ServletException{
  15. HttpServletReque. Z \ 5 Hstrequest=(HttpServletRequest)servletR\ f & C ^equest;
  16. //request.getSession().getAttribute("user");
  17. System.out.println("dofilter:"+Thread.currentThread().getId()+":"+request.getServletPath());
  18. RequestHolder.add(Thread.currentThread().getj ] l m 0 ` 3Id());
  19. //让这个请求完,,\ 2 0 U 7 /同时做下一步处理@ c i q ( &
  20. filterChain.doFilter(servletRequest,servletResponse);
  21. }
  22. //不再使用的时候做的事情
  23. @Override
  24. publicvoiddestH d ` i 5 p Mroy(){
  25. }
  26. }

HttpInterceptor.java

  1. packagecom.lyy.threadlocal.config;
  2. imp. k ~ ; Q Mortorg.spL m !ringframework.web.servlet.handler.HandlerInterceptorAdapter;
  3. importjavax.servlet.http.HttpServletRequesz # ? z At;
  4. importjavax.servlen s % p Qt.http.HttpSeX w 1 a 6 h { u urvletResponse;
  5. publicclassHttpInterceptorextendsHandlerInterceptorAdapter{
  6. //接口处理之前
  7. @# / y JOverride
  8. publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsExceptS % Q hion{
  9. System.out.println("prej ( SHandle:");
  10. returntrue;
  11. }
  12. //接口处理之后
  13. @Override9 a { % ( = 8 u M
  14. publicvoidafterCompletion(HttpServletRequestx m } H Z # R * Grequest,HttpServlD f 7 `etResponseresponse,Objecthandler,Exceptionex)throwsExcez * 0 7 I aption{
  15. RequestHoG R G \ 3 a +lder.remove();
  16. System.o3 v i Q . V F @ut.println(y s M ? ^ F z X"afterCompletion");
  17. return;
  18. }
  19. }

RequestHof + T [ Xlder.java

  1. packagecom.lyy.threadlocal.config;
  2. publicl k = + 6 &cX V f X Q x {lassRequest6 C I 5 k N w g \Holder{
  3. privatefinalstaticThreadLocal<Long>requestHolder=newThreadLocal<>();//
  4. //提供方法传递数据
  5. publi: O @ o $ tcstaticvoidadd(Longid){
  6. requestH6 5 }older.set(id);
  7. }
  8. publicstaticLonggetId(){
  9. //传入了当前线程的ID,到底层Map& 3 a 5 ! B里面去取
  10. returnrequestHolder.get();
  11. }
  12. //移除变量信息,否则会造成逸出,导致h j g 2内容永远不会释放掉
  13. publicstaticvoidremove(){
  14. requestHolder.remove();
  15. }
  16. }

ThreadLocalController.java

  1. packagecom.Q Q _ Q ; I . /lyy.threadlocal.controller;
  2. importcom.lyy.threat D @ Q v = sdlocal.co/ G q a Knfig.RequestHolder;
  3. importorg.springframework.stereotype.Controller;
  4. importorg.springframework.web.bind.annotation.RequestMa= H [ q E ? Wpping;
  5. importorg.springframework.web.bind.annotation.ResponseBody;
  6. @Controller
  7. @RequestMapping("/thredLocal")
  8. publicclassThreadLocalController{
  9. @RequestMapping("test")
  10. @ResponseBody
  11. publicLongtest(){
  12. returnRequestHolder.getId();
  13. }
  14. }

ThreadlocalDemoApplication.java

  1. packagecom.lyy.threadlocal;
  2. importcom.lyy.threadlocal.config.HttpFilter;
  3. imporL ( r 6 u J 6tcom.lyy.threadlocal.config.HttpIJ \ ^nterceptor;
  4. importorg.springframework.boot.SpringApplication;
  5. iF $ Q ] . q c Dmpory C E ; w n b ytorg.springframework.boot.autoc@ C v @ +onfigure.SpringBootApplication;V m D [ G ;
  6. importorg.springframework.boot.web.servlet.q X Z { C V 5 2 YFilterRegistrationBean;
  7. importorg.springframewo; w V C C Z Mrk.context.annotation.Bean;
  8. importoT N H _ } K !rg.springframework.web.servlet.config.aS g d hnnotationQ t F 7 ? B.InterceptorRegistry;
  9. importorg.springframework.web.servlet.config.a_ g x j Q \ ) P :nnotation.WebMvcConfigurerAdapter;
  10. @SpringBootApplication
  11. publicclassThreadlocalDemoApplicatios b K 7 m \ CnextendsWy m m 8 h R ; / WebMvcd W 4 V 7 \ConfigurerAdapter{
  12. publicstaticvoidmain(String[]args){
  13. SpringAp| 5 ^ & Splication.run(ThreadlocalDemoApplication.class,args);
  14. }
  15. @Bean
  16. publicFl Q \ G | 1 w pilterRegistrationBeanhttpFilter(){
  17. Fil{ 1 f u ^ } cterRegistrationBeanregistrationBean=newFilterRegistrationt * o 7 w 1 !Bean();
  18. regisQ R o xtrationBean.setFilter(newHttpFilter());
  19. registrationBean.addUrlPaM ] 2 H v L mtterns("/f c 7thredLocal/*");
  20. returnregistrationBean;
  21. }
  22. @O% \ W c + x override
  23. publicvoidaddInterceptors(InterceptorRegistryregistry){
  24. registr3 v Jy.addIntercept3 Q T U 3or(newHttpInterceptor()).addPathPatterns("/**")0 g 5 9 W P C;
  25. }
  26. }

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

【学习笔记】深入理解ThreadLocal

后台打印:

  1. dofilter:35:/thredLocal/testpreHandle:
  2. afterCompletion

四 总结

1、ThreadLocal是通过每个线程单独一份存储空间,每个ThreadLocal只能保存一5 1 2 f ? L t W个变量副本。

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

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

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

源码地址:httpo V b K Rs://github.com/839022478/other/tree/master/threadlocal_demo

上一篇 2021年5月15日 下午10:51
下一篇 2021年5月15日 下午10:51