ThreadLocal 是线程专属的本地变量它的设计是给每个线程创建一份独立的变量副本线程之间完全不共享数据天然实现线程隔离全程无锁竞争性能远高于加锁保证线程安全的方案。每个Thread内部都有一个专属的成员变量ThreadLocalMap不是ThreadLocal的Key是ThreadLocal实例本身弱引用Value是存的变量强引用。官话太多直接上例子。以用户上下文传递举个例子/** * 用户上下文工具类 * 用ThreadLocal存当前登录用户的ID避免层层传参 */publicclassUserContextHolder{// 定义ThreadLocal存用户IDprivatestaticfinalThreadLocalLongUSER_ID_THREAD_LOCALnewThreadLocal();/** * 设置用户ID */publicstaticvoidsetUserId(LonguserId){USER_ID_THREAD_LOCAL.set(userId);}/** * 获取用户ID */publicstaticLonggetUserId(){returnUSER_ID_THREAD_LOCAL.get();}/** * 必须手动调用清理ThreadLocal防止内存泄漏 */publicstaticvoidclear(){USER_ID_THREAD_LOCAL.remove();}}和加锁保证线程安全的区别对比项加锁ThreadLocal思想多线程排队用同一个变量保证同一时间只有一个线程能访问每个线程用自己的专属变量完全不共享性能有锁竞争高并发下性能差无锁竞争性能高适用场景多线程必须共享同一个变量的场景比如共享计数器、共享资源修改每个线程需要独立变量副本的场景比如用户上下文、SimpleDateFormat 线程安全小贴士很多人以为ThreadLocalMap的Key是“线程ID”其实Key是ThreadLocal实例本身而不是线程ID。一个线程可以有多个ThreadLocal实例每个实例对应一个Value存到同一个ThreadLocalMap里Key不同互不干扰。ThreadLocal内存泄漏问题Java中的4种引用强度强引用我们平时写的Object obj new Object()就是强引用只要强引用还在GC就不会回收这个对象。软引用用SoftReference包装的引用只有当内存不够用的时候GC 才会回收这个对象。弱引用用WeakReference包装的引用只要GC一运行不管内存够不够都会回收这个对象。虚引用用PhantomReference包装的引用完全不影响对象的生命周期只是用来在对象被 GC 回收前收到一个通知做一些收尾工作。ThreadLocalMap的Entry结构ThreadLocalMap的Entry是继承自WeakReference的结构如下staticclassEntryextendsWeakReferenceThreadLocal?{Objectvalue;Entry(ThreadLocal?k,Objectv){super(k);// Key是弱引用valuev;// Value是强引用}}假设创建了一个ThreadLocal实例有外部强引用指向它同时把它作为 Key 存到了线程的ThreadLocalMap里当用完ThreadLocal后没有手动remove而且外部的强引用也没了比如threadLocal null因为Key是弱引用GC 一运行Key就被回收了变成了null。但是Value是强引用只要线程还活着Value就不会被 GC 回收这就导致了内存泄漏。更严重的还有如果用了线程池线程会被长期复用不仅内存泄漏还会导致用户信息串号、数据错误等严重的业务问题。怎么避免内存泄漏使用完ThreadLocal后手动调用remove()方法。举个例子在Spring Boot的拦截器里ComponentpublicclassUserInterceptorimplementsHandlerInterceptor{OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler){// 从Token里解析出用户ID存到ThreadLocalLonguserIdparseUserIdFromToken(request.getHeader(Authorization));UserContextHolder.setUserId(userId);returntrue;}OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex){// 在这里清理不管请求成功失败都要清理。UserContextHolder.clear();}}
【面试真题拆解】你知道ThreadLocal是什么吗
ThreadLocal 是线程专属的本地变量它的设计是给每个线程创建一份独立的变量副本线程之间完全不共享数据天然实现线程隔离全程无锁竞争性能远高于加锁保证线程安全的方案。每个Thread内部都有一个专属的成员变量ThreadLocalMap不是ThreadLocal的Key是ThreadLocal实例本身弱引用Value是存的变量强引用。官话太多直接上例子。以用户上下文传递举个例子/** * 用户上下文工具类 * 用ThreadLocal存当前登录用户的ID避免层层传参 */publicclassUserContextHolder{// 定义ThreadLocal存用户IDprivatestaticfinalThreadLocalLongUSER_ID_THREAD_LOCALnewThreadLocal();/** * 设置用户ID */publicstaticvoidsetUserId(LonguserId){USER_ID_THREAD_LOCAL.set(userId);}/** * 获取用户ID */publicstaticLonggetUserId(){returnUSER_ID_THREAD_LOCAL.get();}/** * 必须手动调用清理ThreadLocal防止内存泄漏 */publicstaticvoidclear(){USER_ID_THREAD_LOCAL.remove();}}和加锁保证线程安全的区别对比项加锁ThreadLocal思想多线程排队用同一个变量保证同一时间只有一个线程能访问每个线程用自己的专属变量完全不共享性能有锁竞争高并发下性能差无锁竞争性能高适用场景多线程必须共享同一个变量的场景比如共享计数器、共享资源修改每个线程需要独立变量副本的场景比如用户上下文、SimpleDateFormat 线程安全小贴士很多人以为ThreadLocalMap的Key是“线程ID”其实Key是ThreadLocal实例本身而不是线程ID。一个线程可以有多个ThreadLocal实例每个实例对应一个Value存到同一个ThreadLocalMap里Key不同互不干扰。ThreadLocal内存泄漏问题Java中的4种引用强度强引用我们平时写的Object obj new Object()就是强引用只要强引用还在GC就不会回收这个对象。软引用用SoftReference包装的引用只有当内存不够用的时候GC 才会回收这个对象。弱引用用WeakReference包装的引用只要GC一运行不管内存够不够都会回收这个对象。虚引用用PhantomReference包装的引用完全不影响对象的生命周期只是用来在对象被 GC 回收前收到一个通知做一些收尾工作。ThreadLocalMap的Entry结构ThreadLocalMap的Entry是继承自WeakReference的结构如下staticclassEntryextendsWeakReferenceThreadLocal?{Objectvalue;Entry(ThreadLocal?k,Objectv){super(k);// Key是弱引用valuev;// Value是强引用}}假设创建了一个ThreadLocal实例有外部强引用指向它同时把它作为 Key 存到了线程的ThreadLocalMap里当用完ThreadLocal后没有手动remove而且外部的强引用也没了比如threadLocal null因为Key是弱引用GC 一运行Key就被回收了变成了null。但是Value是强引用只要线程还活着Value就不会被 GC 回收这就导致了内存泄漏。更严重的还有如果用了线程池线程会被长期复用不仅内存泄漏还会导致用户信息串号、数据错误等严重的业务问题。怎么避免内存泄漏使用完ThreadLocal后手动调用remove()方法。举个例子在Spring Boot的拦截器里ComponentpublicclassUserInterceptorimplementsHandlerInterceptor{OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler){// 从Token里解析出用户ID存到ThreadLocalLonguserIdparseUserIdFromToken(request.getHeader(Authorization));UserContextHolder.setUserId(userId);returntrue;}OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex){// 在这里清理不管请求成功失败都要清理。UserContextHolder.clear();}}