一、ThreadLocal基本介绍和应用1.1 基本介绍作用:ThreadLocal意为线程本地变量用于解决多线程并发时访问共享变量的问题。ThreadLocal与synchronized锁)比较:应用场景:1、可用于保存线程不安全的工具类典型的需要使用的类就是SimpleDateFormat。2、保存一些业务内容如用户权限信息、从用户系统获取到的用户名、用户ID 等。3、保存和线程绑定的信息比如用来保存资源的处理进度。常用方法:set()在当前线程范围内设置一个值存储到ThreadLocal中这个值仅对当前线程可见。相当于在当前线程范围内建立了副本。get()从当前线程范围内取出set方法设置的值.remove()移除当前线程中存储的值withInitialjava8中的初始化方法1.2 ThreadLocal与Thread关系1.3 ThreadLocal应用案例ThreadLocalDemo.javapackage cn.zxj.threadlocal;importjava.text.DateFormat;importjava.text.ParseException;importjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;public class ThreadLocalDemo{//非线程安全的 private static final SimpleDateFormat sdfnew SimpleDateFormat(yyyy-MM-dd HH:mm:ss);private static ThreadLocalDateFormatdateFormatThreadLocalnew ThreadLocal();private static DateFormatgetDateFormat(){DateFormat dateFormatdateFormatThreadLocal.get();//从当前线程的范围内获得一个DateFormatif(dateFormatnull){dateFormatnew SimpleDateFormat(yyyy-MM-dd HH:mm:ss);//Thread.currentThread();dateFormatThreadLocal.set(dateFormat);//要在当前线程的范围内设置一个simpleDateFormat对象.}returndateFormat;}public static Date parse(String strDate)throws ParseException{returngetDateFormat().parse(strDate);}public static void main(String[]args){ExecutorService executorServiceExecutors.newFixedThreadPool(10);for(int i0;i20;i){executorService.execute(()-{ try {//sdf.parse(2021-05-3020:12:20);System.out.println(parse(2021-05-3020:12:20));}catch(ParseException e){e.printStackTrace();}});}}}ThreadLocalExample.javapackage cn.zxj.threadlocal;public class ThreadLocalExample{//希望每一个线程获得的num都是0 // private static intnum0;//全局用户信息的存储 static ThreadLocalIntegerlocalThreadLocal.withInitial(()-0);public static void main(String[] args){ Thread[] threadnew Thread[5];for(int i0;i5;i){ thread[i]new Thread(()-{ int numlocal.get().intValue();local.set(num5);System.out.println(Thread.currentThread().getName() local.get());});}//localnull;for(int i0;i5;i){thread[i].start();}}}二、ThreadLocal源码分析2.1 set方法分析public void set(T value){Thread tThread.currentThread();// 如果当前线程已经初始化了map。 // 如果没有初始化则进行初始化。 ThreadLocalMap mapgetMap(t);if(map!null)//修改value map.set(this, value);else//初始化 createMap(t, value);}void createMap(Thread t, T firstValue){t.threadLocalsnew ThreadLocalMap(this, firstValue);}ThreadLocalMap(ThreadLocal?firstKey, Object firstValue){tablenew Entry[INITIAL_CAPACITY];//默认长度为16的数组 int ifirstKey.threadLocalHashCode(INITIAL_CAPACITY -1);//计算数组下标 table[i]new Entry(firstKey, firstValue);//把key/value存储到i的位置. size1;setThreshold(INITIAL_CAPACITY);}private void set(ThreadLocal?key, Object value){// We dont 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,inwhichcase, a fast // path would failmoreoften than not. Entry[]tabtable;int lentab.length;int ikey.threadLocalHashCode(len-1);//计算数组下标()//线性探索.()for(Entry etab[i];e!null;etab[inextIndex(i, len)]){ThreadLocal?ke.get();// i的位置已经存在了值 就直接替换。if(kkey){e.valuevalue;return;}//如果keynull则进行replaceStaleEntry(替换空余的数组)if(knull){replaceStaleEntry(key, value, i);return;}}tab[i]new Entry(key, value);int szsize;if(!cleanSomeSlots(i, sz)szthreshold)rehash();}2.2 replaceStaleEntry方法分析/* 把当前的value保存到entry数组中 清理无效的key(将value置为null)*/ private void replaceStaleEntry(ThreadLocal?key, Object value, int staleSlot){Entry[]tabtable;int lentab.length;Entry e;// Back up to checkforprior stale entryincurrent run. // We clean out whole runs at atimeto avoid continual // incremental rehashing due to garbage collector freeing // up refsinbunches(i.e., whenever the collector runs). int slotToExpungestaleSlot;for(int iprevIndex(staleSlot, len);(etab[i])!null;iprevIndex(i, len))if(e.get()null)slotToExpungei;// Find either the key or trailing null slot of run, whichever // occurs firstfor(int inextIndex(staleSlot, len);(etab[i])!null;inextIndex(i, len)){ThreadLocal?ke.get();// If wefindkey,thenwe need to swap it // with the stale entry to maintainhashtable order. // The newly stale slot, or any other stale slot // encountered above it, canthenbe sent to expungeStaleEntry // to remove or rehash all of the other entriesinrun.if(kkey){e.valuevalue;tab[i]tab[staleSlot];tab[staleSlot]e;// Start expunge at preceding stale entryifit existsif(slotToExpungestaleSlot)slotToExpungei;cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);return;}// If we didntfindstale entry on backward scan, the // first stale entry seenwhilescanningforkey is the // first still presentinthe run.if(knullslotToExpungestaleSlot)slotToExpungei;}// If key not found, put new entryinstale slot tab[staleSlot].valuenull;tab[staleSlot]new Entry(key, value);// If there are any other stale entriesinrun, expunge themif(slotToExpunge!staleSlot)cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);}如果当前值对应的entry数组中key为null那么该方法会向前查找到还存在key失效的entry进行清理。向后通过线性探测法解决hash冲突。开放定址法也称为线性探测法就是从发生冲突的那个位置开始按照一定的次序从hash表中找到一个空闲的位置然后把发生冲突的元素存入到这个空闲位置中。2.3 replaceStaleEntry方法图解前置说明是先向前查找再向后查找(是串行不是并行)场景一:向前有脏Entry向后查找到可覆盖的Entry(和插入元素相同key的元素)左边循环在staleSlot(插入点)左边空entry停下终止slotToExpunge(删除点)是离左边空entry最近的一个脏entry。右边循环在staleSlot(插入点)右边找到可覆盖的entry进行覆盖并将覆盖后的entry和staleSlot(插入点)的entry交换位置然后会从slotToExpunge(删除点)开始往后清理脏entry。注意:覆盖后的entry和staleSlot(插入点)的entry之所以要交换位置是为了避免staleSlot(插入点)脏entry被清理后下次能直接插入相同key的entry从而导致重复数据。场景二:向前有脏Entry向后未找到可覆盖的Entry左边循环在staleSlot(插入点)左边空entry停止slotToExpunge(删除点)是离左边空entry最近的一个脏entry。右边循环在staleSlot(插入点)右边未找到可覆盖的entry并在右边空entry停止。最后将staleSlot(插入点)的entry的value置为null并根据传入的key和value创建新的entry放入到staleSlot位置然后从slotToExpunge(删除点)开始往后清理脏entry。场景三:向前没有脏Entry向后找到可覆盖的Entry左边循环在staleSlot(插入点)左边空entry停下终止并且没有找到脏entry此时slotToExpunge(删除点)和staleSlot(插入点)重合。右边循环在staleSlot(插入点)右边找到可覆盖的entry进行覆盖并将覆盖后的entry和staleSlot(插入点)的entry交换位置与此同时还会将slotToExpunge(删除点)移到右边离staleSlot(插入点)最近的脏entry处或者是移到可覆盖的Entry处然后从slotToExpunge(删除点)开始往后清理脏entry。场景四:向前没有脏Entry向后未找到可覆盖的Entry左边循环在staleSlot(插入点)左边空entry停下终止并且没有找到脏entry此时slotToExpunge(删除点)和staleSlot(插入点)重合。右边循环在staleSlot(插入点)右边未找到可覆盖的entry与此同时还会将slotToExpunge(删除点)移到右边离staleSlot(插入点)最近的脏entry处直到遇到空entry结束循环。最后将staleSlot(插入点)的entry的value置为null并根据传入的key和value创建新的entry放入到staleSlot位置然后从slotToExpunge(删除点)开始往后清理脏entry。2.4 总结set方法总结1、如果没有初始化ThreadLocalMap则进行初始化。1.1、创建默认长度为16的Entry数组1.2、与运算计算数组下标i(threadLocalHashCode(数组长度-1))1.3、将key/value存储到计算的下标i的位置(key为TherdLocal的弱引用)2、如果已经初始化ThreadLocalMap则进行set操作2.1、与运算计算数组下标i(threadLocalHashCode(数组长度-1))2.2、i的位置没有元素则直接设置2.3、i的位置有相同key的元素则直接替换2.4、i的位置有脏entry(key为null)的元素则调用replaceStaleEntry方法解决hash冲突replaceStaleEntry方法是基于线性探测法解决hash冲突的具体参考replaceStaleEntry方法图解脏数据由于entry中的key是采用弱引用指向ThreadLocal实例的因此ThreadLocal实例被GC后entry中的key的会变为null但是value还是有值就会导致该entry失效因此是需要对key为null的entry进行清理。内存泄漏通过源码分析我们知道 expungeStaleEntry() 方法是帮助垃圾回收的根据源码我们可以发现get 和set 方法都可能触发清理方法 expungeStaleEntry() 所以正常情况下是不会有内存泄漏的但是如果我们没有调用get 和set 的时候就会可能面临着内存泄漏养成好习惯不再使用的时候调用remove()加快垃圾回收避免内存泄漏。退一步说就算我们没有调用get 和set 和remove 方法线程结束的时候也就没有强引用再指向ThreadLocalMap了这样ThreadLocalMap 和里面的元素也会被回收掉但是有一种危险情况是如果线程是线程池的在线程执行完代码的时候并没有结束只是归还给线程池这个时候ThreadLocalMap 和里面的元素是不会回收掉的。