做临时网站泰兴网页定制

张小明 2026/1/9 12:16:17
做临时网站,泰兴网页定制,电脑如何安装免费wordpress,公众号上传wordpressThreadLocal深入剖析 前言 在并发编程中#xff0c;当多个线程同时操作一个共享变量#xff0c;就会出现线程安全问题。常见的解决方案是加锁#xff0c;但锁会带来性能开销#xff0c;尤其在高并发场景下。今天要讲的 ThreadLocal#xff0c;提供了另一种思路#xff…ThreadLocal深入剖析前言在并发编程中当多个线程同时操作一个共享变量就会出现线程安全问题。常见的解决方案是加锁但锁会带来性能开销尤其在高并发场景下。今天要讲的ThreadLocal提供了另一种思路让每个线程拥有自己独立的变量副本从根本上避免竞争。个人主页你的主页文章目录ThreadLocal深入剖析一、线程安全问题的两种场景二、ThreadLocal的正确使用场景三、ThreadLocal核心API四、底层数据结构演进五、ThreadLocal内存泄漏问题六、实战最佳实践七、InheritableThreadLocal八、总结一、线程安全问题的两种场景在讨论 ThreadLocal 之前我们先搞清楚什么场景该用锁什么场景该用 ThreadLocal。1.1 场景一多线程竞争修改同一个值场景描述电商系统的商品秒杀100 件商品多个用户同时抢购。publicclassSeckillService{privateintstock100;// 库存共享变量publicvoidseckill(){if(stock0){stock--;// 扣减库存System.out.println(Thread.currentThread().getName() 抢购成功剩余库存stock);}else{System.out.println(Thread.currentThread().getName() 抢购失败库存不足);}}}问题两个线程同时判断stock 0都通过了然后都执行stock--导致超卖。线程A读取 stock 1判断 0准备扣减 线程B读取 stock 1判断 0准备扣减 线程Astock-- → stock 0 线程Bstock-- → stock -1 ❌ 超卖了解决方案加锁publicsynchronizedvoidseckill(){if(stock0){stock--;System.out.println(Thread.currentThread().getName() 抢购成功);}}能用 ThreadLocal 吗不能因为 ThreadLocal 会给每个线程创建独立副本每个线程都有自己的stock 100那每个人都能抢 100 件这显然不对。结论当多个线程需要竞争修改同一个值时必须用锁不能用 ThreadLocal。1.2 场景二每个线程需要独立的上下文数据场景描述Web 系统中每个请求需要携带当前登录用户的信息在整个请求链路中随时可以获取。用户A发起请求 → 拦截器解析Token获取用户A信息 → Controller → Service → DAO 用户B发起请求 → 拦截器解析Token获取用户B信息 → Controller → Service → DAO如果用普通的共享变量存储用户信息publicclassUserContext{publicstaticUsercurrentUser;// 共享变量}问题线程A设置 currentUser 用户A 线程B设置 currentUser 用户B ← 覆盖了 线程A获取 currentUser → 拿到的是用户B ❌ 数据错乱解决方案ThreadLocalpublicclassUserContext{privatestaticfinalThreadLocalUsercurrentUsernewThreadLocal();publicstaticvoidsetUser(Useruser){currentUser.set(user);}publicstaticUsergetUser(){returncurrentUser.get();}}每个线程都有自己独立的 User 副本互不干扰。结论当每个线程需要独立的上下文数据且这个数据在整个线程生命周期内需要被多处访问时用 ThreadLocal。1.3 两种场景对比场景特点解决方案秒杀库存多线程竞争修改同一个值加锁synchronized/Lock用户上下文每个线程需要独立的值ThreadLocal一句话总结锁让多个线程排队操作同一个变量ThreadLocal让每个线程各自操作自己的变量二、ThreadLocal的正确使用场景2.1 场景一请求上下文传递这是 ThreadLocal 最经典的使用场景。需求在 Web 应用中用户登录后整个请求链路都需要获取当前用户信息。传统做法把用户信息作为参数层层传递// ControllerpublicvoidcreateOrder(Useruser,OrderDTOorderDTO){orderService.createOrder(user,orderDTO);}// ServicepublicvoidcreateOrder(Useruser,OrderDTOorderDTO){// 业务逻辑orderDao.insert(user,order);logService.log(user,创建订单);}问题参数传递太繁琐代码侵入性强。ThreadLocal 做法// 定义用户上下文publicclassUserContextHolder{privatestaticfinalThreadLocalUseruserHoldernewThreadLocal();publicstaticvoidsetUser(Useruser){userHolder.set(user);}publicstaticUsergetUser(){returnuserHolder.get();}publicstaticvoidclear(){userHolder.remove();}}// 拦截器中设置publicclassAuthInterceptorimplementsHandlerInterceptor{OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler){Stringtokenrequest.getHeader(Authorization);UserusertokenService.parseToken(token);UserContextHolder.setUser(user);// 存入 ThreadLocalreturntrue;}OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex){UserContextHolder.clear();// 请求结束清理 ThreadLocal}}// 业务代码中随时获取publicvoidcreateOrder(OrderDTOorderDTO){UseruserUserContextHolder.getUser();// 直接获取无需传参// 业务逻辑...}2.2 场景二数据库连接管理需求保证同一个线程内的多次数据库操作使用同一个连接实现事务。publicclassConnectionManager{privatestaticfinalThreadLocalConnectionconnectionHoldernewThreadLocal();publicstaticConnectiongetConnection()throwsSQLException{ConnectionconnconnectionHolder.get();if(connnull){conndataSource.getConnection();connectionHolder.set(conn);}returnconn;}publicstaticvoidcloseConnection(){ConnectionconnconnectionHolder.get();if(conn!null){try{conn.close();}catch(SQLExceptione){e.printStackTrace();}connectionHolder.remove();}}}Spring 的Transactional底层就是用 ThreadLocal 来保证同一事务内使用同一个数据库连接。2.3 场景三日期格式化工具问题SimpleDateFormat不是线程安全的。// 错误示范多线程共享 SimpleDateFormatpublicclassDateUtil{privatestaticfinalSimpleDateFormatsdfnewSimpleDateFormat(yyyy-MM-dd);publicstaticStringformat(Datedate){returnsdf.format(date);// 多线程调用会出问题}}ThreadLocal 解决publicclassDateUtil{privatestaticfinalThreadLocalSimpleDateFormatdateFormatHolderThreadLocal.withInitial(()-newSimpleDateFormat(yyyy-MM-dd));publicstaticStringformat(Datedate){returndateFormatHolder.get().format(date);// 每个线程用自己的实例}}2.4 场景四链路追踪 TraceId需求分布式系统中一个请求会经过多个服务需要用 TraceId 串联整个调用链路。publicclassTraceContext{privatestaticfinalThreadLocalStringtraceIdHoldernewThreadLocal();publicstaticvoidsetTraceId(StringtraceId){traceIdHolder.set(traceId);}publicstaticStringgetTraceId(){returntraceIdHolder.get();}publicstaticvoidclear(){traceIdHolder.remove();}}// 在日志中自动带上 TraceIdpublicvoiddoSomething(){log.info([{}] 开始处理业务,TraceContext.getTraceId());// 业务逻辑...}三、ThreadLocal核心API3.1 基本方法ThreadLocalStringthreadLocalnewThreadLocal();// 设置值threadLocal.set(hello);// 获取值StringvaluethreadLocal.get();// hello// 移除值重要防止内存泄漏threadLocal.remove();3.2 初始值设置方式一重写 initialValue 方法ThreadLocalIntegercounternewThreadLocalInteger(){OverrideprotectedIntegerinitialValue(){return0;}};方式二使用 withInitial推荐Java 8ThreadLocalIntegercounterThreadLocal.withInitial(()-0);ThreadLocalListStringlistHolderThreadLocal.withInitial(ArrayList::new);3.3 完整示例publicclassThreadLocalDemo{privatestaticfinalThreadLocalIntegercounterThreadLocal.withInitial(()-0);publicstaticvoidmain(String[]args){// 线程1newThread(()-{for(inti0;i3;i){intvaluecounter.get();counter.set(value1);System.out.println(Thread.currentThread().getName(): counter.get());}},Thread-A).start();// 线程2newThread(()-{for(inti0;i3;i){intvaluecounter.get();counter.set(value1);System.out.println(Thread.currentThread().getName(): counter.get());}},Thread-B).start();}}输出顺序可能不同但每个线程独立计数Thread-A: 1 Thread-A: 2 Thread-A: 3 Thread-B: 1 Thread-B: 2 Thread-B: 3两个线程各自从 0 开始计数互不影响。四、底层数据结构演进4.1 Java 8 之前的设计ThreadLocal 对象 └── ThreadLocalMap ├── Entry(Thread-A, value-A) ├── Entry(Thread-B, value-B) └── Entry(Thread-C, value-C)特点ThreadLocal 维护一个 MapKey 是 Thread 对象Value 是变量副本问题需要对 Map 加锁因为多个线程会同时操作这个 Map线程销毁后对应的 Entry 不会自动清理4.2 Java 8 之后的设计Thread-A 对象 └── ThreadLocalMap ├── Entry(ThreadLocal-1, value-1) ├── Entry(ThreadLocal-2, value-2) └── Entry(ThreadLocal-3, value-3) Thread-B 对象 └── ThreadLocalMap ├── Entry(ThreadLocal-1, value-1) └── Entry(ThreadLocal-2, value-2)特点每个 Thread 对象内部维护一个 ThreadLocalMapKey 是 ThreadLocal 对象Value 是变量副本优势无需加锁因为每个线程只操作自己的 Map线程销毁时ThreadLocalMap 随之销毁Entry 自动回收4.3 源码分析Thread 类中的字段publicclassThreadimplementsRunnable{// 每个线程都有自己的 ThreadLocalMapThreadLocal.ThreadLocalMapthreadLocalsnull;}ThreadLocal.set() 方法publicvoidset(Tvalue){ThreadtThread.currentThread();// 获取当前线程ThreadLocalMapmapgetMap(t);// 获取当前线程的 ThreadLocalMapif(map!null){map.set(this,value);// Key 是当前 ThreadLocal 对象}else{createMap(t,value);// 首次使用创建 Map}}ThreadLocalMapgetMap(Threadt){returnt.threadLocals;// 返回线程的 threadLocals 字段}ThreadLocal.get() 方法publicTget(){ThreadtThread.currentThread();ThreadLocalMapmapgetMap(t);if(map!null){ThreadLocalMap.Entryemap.getEntry(this);if(e!null){return(T)e.value;}}returnsetInitialValue();// 没有值则返回初始值}4.4 ThreadLocalMap 的结构ThreadLocalMap 是 ThreadLocal 的静态内部类使用数组 线性探测法解决哈希冲突。staticclassThreadLocalMap{staticclassEntryextendsWeakReferenceThreadLocal?{Objectvalue;Entry(ThreadLocal?k,Objectv){super(k);// Key 是弱引用valuev;}}privateEntry[]table;// Entry 数组}关键点Entry 的 KeyThreadLocal 对象是弱引用。五、ThreadLocal内存泄漏问题5.1 为什么会内存泄漏先看 Entry 的结构staticclassEntryextendsWeakReferenceThreadLocal?{Objectvalue;// Value 是强引用}KeyThreadLocal弱引用GC 时会被回收Value强引用不会被自动回收泄漏场景1. ThreadLocal 对象被设为 null 2. GC 回收 ThreadLocal 对象因为是弱引用 3. Entry 的 Key 变成 null但 Value 还在 4. 如果线程一直存活比如线程池这个 Value 永远无法被回收Thread └── ThreadLocalMap └── Entry ├── Key: null已被 GC 回收 └── Value: 还在无法回收← 内存泄漏5.2 为什么 Key 要设计成弱引用如果 Key 是强引用ThreadLocal tl new ThreadLocal(); tl.set(value); tl null; // 想释放 ThreadLocal // 但是 Entry 还持有 ThreadLocal 的强引用 // ThreadLocal 对象无法被回收 ← 更严重的内存泄漏设计成弱引用后至少 ThreadLocal 对象可以被回收只是 Value 还在。5.3 ThreadLocal 的自我清理机制ThreadLocal 在get()、set()、remove()时会顺便清理Key 为 null 的 EntryprivateintexpungeStaleEntry(intstaleSlot){Entry[]tabtable;// 清理 Key 为 null 的 Entrytab[staleSlot].valuenull;tab[staleSlot]null;// ...}但这个清理是被动的如果一直不调用这些方法泄漏的 Value 就一直存在。5.4 正确的使用姿势原则用完必须调用 remove()publicclassUserContextHolder{privatestaticfinalThreadLocalUseruserHoldernewThreadLocal();publicstaticvoidsetUser(Useruser){userHolder.set(user);}publicstaticUsergetUser(){returnuserHolder.get();}// 关键提供 clear 方法publicstaticvoidclear(){userHolder.remove();}}// 使用时try{UserContextHolder.setUser(user);// 业务逻辑...}finally{UserContextHolder.clear();// 必须清理}在 Web 应用中publicclassAuthInterceptorimplementsHandlerInterceptor{OverridepublicbooleanpreHandle(...){UserContextHolder.setUser(user);returntrue;}OverridepublicvoidafterCompletion(...){UserContextHolder.clear();// 请求结束时清理}}六、实战最佳实践6.1 完整的上下文工具类publicclassRequestContext{privatestaticfinalThreadLocalMapString,ObjectCONTEXTThreadLocal.withInitial(HashMap::new);// 设置属性publicstaticvoidset(Stringkey,Objectvalue){CONTEXT.get().put(key,value);}// 获取属性SuppressWarnings(unchecked)publicstaticTTget(Stringkey){return(T)CONTEXT.get().get(key);}// 获取属性带默认值SuppressWarnings(unchecked)publicstaticTTget(Stringkey,TdefaultValue){Tvalue(T)CONTEXT.get().get(key);returnvalue!null?value:defaultValue;}// 移除属性publicstaticvoidremove(Stringkey){CONTEXT.get().remove(key);}// 清空所有重要publicstaticvoidclear(){CONTEXT.remove();}// 常用快捷方法publicstaticvoidsetUserId(LonguserId){set(userId,userId);}publicstaticLonggetUserId(){returnget(userId);}publicstaticvoidsetTraceId(StringtraceId){set(traceId,traceId);}publicstaticStringgetTraceId(){returnget(traceId);}}6.2 配合 Spring 拦截器使用ComponentpublicclassContextInterceptorimplementsHandlerInterceptor{OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler){// 生成 TraceIdStringtraceIdUUID.randomUUID().toString().replace(-,);RequestContext.setTraceId(traceId);// 解析用户信息Stringtokenrequest.getHeader(Authorization);if(StringUtils.hasText(token)){LonguserIdtokenService.parseUserId(token);RequestContext.setUserId(userId);}returntrue;}OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex){// 请求结束必须清理RequestContext.clear();}}6.3 线程池场景的注意事项线程池中的线程是复用的如果不清理 ThreadLocal下一个任务会拿到上一个任务的数据。ExecutorServiceexecutorExecutors.newFixedThreadPool(2);executor.submit(()-{try{RequestContext.setUserId(100L);// 业务逻辑...}finally{RequestContext.clear();// 必须清理}});更优雅的方式封装任务包装器publicclassContextAwareRunnableimplementsRunnable{privatefinalRunnabletask;privatefinalMapString,Objectcontext;publicContextAwareRunnable(Runnabletask){this.tasktask;// 捕获提交任务时的上下文this.contextnewHashMap(RequestContext.getAll());}Overridepublicvoidrun(){try{// 恢复上下文RequestContext.setAll(context);task.run();}finally{// 清理上下文RequestContext.clear();}}}// 使用executor.submit(newContextAwareRunnable(()-{// 可以获取到父线程的上下文LonguserIdRequestContext.getUserId();}));七、InheritableThreadLocal7.1 问题子线程无法获取父线程的 ThreadLocalThreadLocalStringthreadLocalnewThreadLocal();threadLocal.set(父线程的值);newThread(()-{System.out.println(threadLocal.get());// null}).start();子线程无法获取父线程设置的值。7.2 解决方案InheritableThreadLocalInheritableThreadLocalStringinheritableThreadLocalnewInheritableThreadLocal();inheritableThreadLocal.set(父线程的值);newThread(()-{System.out.println(inheritableThreadLocal.get());// 父线程的值}).start();原理创建子线程时会把父线程的 InheritableThreadLocal 值复制一份给子线程。7.3 局限性InheritableThreadLocal 只在创建线程时复制对于线程池场景不适用ExecutorServiceexecutorExecutors.newFixedThreadPool(1);inheritableThreadLocal.set(任务1的值);executor.submit(()-{System.out.println(inheritableThreadLocal.get());// 任务1的值 ✅});inheritableThreadLocal.set(任务2的值);executor.submit(()-{System.out.println(inheritableThreadLocal.get());// 还是任务1的值 ❌});因为线程池复用线程第二个任务用的是已经创建好的线程不会重新复制。解决方案使用阿里开源的TransmittableThreadLocalTTL。八、总结8.1 核心要点要点说明适用场景每个线程需要独立的上下文数据不适用场景多线程竞争修改同一个值底层结构每个 Thread 持有 ThreadLocalMapKey 是 ThreadLocal内存泄漏Key 是弱引用会被回收Value 是强引用不会自动回收最佳实践用完必须调用 remove()8.2 使用场景总结场景示例请求上下文用户信息、租户信息、TraceId数据库连接同一事务内复用连接日期格式化SimpleDateFormat 线程安全问题分布式追踪链路追踪 TraceId 传递8.3 ThreadLocal vs 锁对比项ThreadLocal锁synchronized/Lock解决思路空间换时间每个线程一份副本时间换安全排队访问适用场景线程隔离各自操作各自的数据线程同步共同操作同一份数据性能无竞争性能高有竞争性能相对低数据一致性不保证各自独立保证同一份数据记住ThreadLocal 不是用来解决共享变量的线程安全问题的而是用来实现线程隔离的。热门专栏推荐Agent小册Java基础合集Python基础合集Go基础合集大数据合集前端小册数据库合集Redis 合集Spring 全家桶微服务全家桶数据结构与算法合集设计模式小册消息队列合集等等等还有许多优秀的合集在主页等着大家的光顾感谢大家的支持文章到这里就结束了如果有什么疑问的地方请指出诸佬们一起来评论区一起讨论希望能和诸佬们一起努力今后我们一起观看感谢您的阅读如果帮助到您不妨3连支持一下创造不易您们的支持是我的动力
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

建立手机也可浏览的网站教程山东天元集团有限公司

一键获取百度网盘提取码:超便捷查询工具使用全攻略 【免费下载链接】baidupankey 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 在日常使用百度网盘时,你是否经常遇到这样的困扰:收到朋友分享的学习资料链接&#xff0c…

张小明 2026/1/6 10:17:47 网站建设

做推文的网站备案编号不放在网站

VB 2005与Visual C++ 2005开发嵌入式应用指南 1. VB 2005开发嵌入式应用概述 VB 2005在嵌入式应用开发中表现出色。借助CE操作系统、.NET Compact Framework,它既能支持开发功能丰富的图形用户界面应用,也能为无头设备开发控制台应用。利用这些特性,开发者可以快速为CE设备…

张小明 2026/1/8 7:21:09 网站建设

企业网站免费建站延庆县专业网站制作网站建设

想要在Linux系统中高效处理文本数据?GNU coreutils中的正则表达式工具就是你的终极武器!无论你是技术新手还是普通用户,掌握grep、sed和awk这三大神器,就能轻松应对各种文本处理需求。GNU coreutils作为Linux系统的基础工具集&…

张小明 2026/1/6 11:35:04 网站建设

模板建网站多少钱电商网站设计思想

阿里自研Wan2.2-T2V-A14B在影视预演中的应用实践 在一部科幻大片的前期筹备会议上,导演指着分镜板上一张手绘草图说:“我希望这个镜头是慢动作,主角从爆炸火光中跃出,风衣翻飞,背景城市崩塌。”传统流程下,…

张小明 2026/1/6 14:19:40 网站建设

网站建设常见故障wordpress可是可视化编辑

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 请对比展示手动编写和AI生成的Nginx WebSocket配置差异:1. 左侧显示开发者手动编写的常见配置(故意包含3个典型错误:缺少proxy_http_version、错…

张小明 2026/1/6 14:19:38 网站建设

数字城市建设网站潍坊做网站软件

增长智能体的出现为企业的智慧转型提供了全新的机遇。它通过整合数据分析、自动化处理和智能决策功能,使得企业能够在复杂多变的市场环境中快速适应。这种技术工具不仅提高了运营效率,还能更好地满足客户需求。例如,企业可以实时监测市场动态…

张小明 2026/1/6 14:19:35 网站建设