尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
并发编程之ThreadLocal
一、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 和里面的元素是不会回收掉的。
RELATED

相关推荐

用vscode里面的latex插件编译AI编译成功的tex,结果报错

用vscode里面的latex插件编译AI编译成功的tex,结果报错

报错:Initial Win CP for (console input, console output, system): (CP936, CP936, CP936) I changed them all to CP936 xxx Latexmk: All targets (xxx.pdf) are up-to-date Latexmk: Sometimes, the -f option can be used to get latexmkto try to force comp…

📅 2026/9/15 11:24:33
并发编程之Fork/Join框架

并发编程之Fork/Join框架

一、基本介绍 1.1 基本介绍 Fork/Join框架是Java 7提供的一个用于并行执行任务的框架, 核心思想就是把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果,其实现思想与MapReduce有异曲同工之妙。 Fork就是把一个大任务切分为…

📅 2026/10/14 10:53:51
168、样本分配中忽略样本 ignore sample 的阈值消融:忽略区域大小对训练稳定性影响

168、样本分配中忽略样本 ignore sample 的阈值消融:忽略区域大小对训练稳定性影响

168、样本分配中忽略样本 ignore sample 的阈值消融:忽略区域大小对训练稳定性影响 从一次诡异的loss震荡说起 去年年底调一个YOLOv11的工业检测模型,训练到第80个epoch左右,loss突然像抽风一样上下乱跳,mAP从0.78直接掉到0.62,然后慢慢爬回来,再掉。我盯着tensorboard看…

📅 2026/10/8 7:56:47
MORE NEWS

更多资讯

📰

C语言猜数字游戏源码详解:随机数与输入校验实战

简介:这份资源是C语言“猜数字”游戏的源程序,压缩包内包含两个不同版本的cpp文件,适合刚接触C语言、希望巩固随机数生成、条件判断、循环控制与输入输出等核心知识的初学者参考。包内共2个文件,合计大小约3KB,均为C/C…

📰

ASPDF组件:Classic ASP环境下COM方式生成PDF的实战指南

简介:ASPDF 是一款专为 Classic ASP 环境设计的开源 PDF 生成工具,面向仍维护或迁移遗留 IIS 应用的中高级 Web 开发者,解决其在无 PHP 或第三方组件支持下动态生成 PDF 的核心痛点。资源包共 50 个文件,含 26 个 JavaScript 文件…

📰

Oracle Data Guard 切换前就绪度检查与基线对比

简介:dgreadiness_v3.6.zip 面向在 Windows 10 上使用 VMware Workstation 运行 Ubuntu 等虚拟机的开发与运维人员,针对关闭 Hyper-V 后仍提示 VMware 与 Device/Credential Guard 不兼容、虚拟机无法启动的问题,提供基于 Windows Defender D…

📰

云ERP WebAPI接口对接:鉴权、单据保存与查询字段映射

简介:这份资料包面向需要接入金蝶云星空新版WebAPI的Java、.NET与Python开发者,覆盖从环境搭建到接口调试的常见需求,适合刚接触星空开放平台、希望快速跑通测试工程与SDK示例的技术人员。压缩包共49个文件,约6.93MB,包…

📰

国产x86集显Win10驱动:INF安装、pnputil回滚与验收

简介:兆芯KX-6000系列显卡驱动面向升级Windows 10后出现显示适配器无法识别、显卡驱动缺失的联想开天系列笔记本用户,尤其适用于系统能通过第三方工具安装网卡驱动、却检测不到显卡驱动的场景。压缩包共29个文件、约19.48MB,以19个dll为核心驱…

📰

C#电子秤数据接口中间件:多型号SDK兼容与串口粘包解析

简介:这份托利多中间件电子秤数据接口C#源码包,面向需要在Windows平台对接托利多电子秤的开发者,重点解决RL00、BCOM等多型号设备的数据读取、指令交互与业务系统集成问题,源码已测试通过并附带厂家SDK。包内以C#源码、项目工程和…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬