Java并发学习总结:ThreadLocal
本文是学习尚硅谷周阳老师《JUC并发编程》的总结(文末有链接)。
是什么
java.lang.ThreadLocal
Java API 文档中的描述:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
补充:调用链使用的 traceId 一般也使用 ThreadLocal 存储。
能干吗
实现每一个线程都有自己专属的本地变量副本,不用考虑多个线程访问同一个共享变量的线程安全问题。
使用示例
使用 ThreadLocal 保存接口请求的 userId、traceId等信息,这样就不需要通过方法参数传递这些信息。
(1)定义资源类
package juc.threadlocal;
public class CustomContext {
private static ThreadLocal<String> userIdHolder = ThreadLocal.withInitial(() -> "");
public static void setUserId(String userId) {
userIdHolder.set(userId);
}
public static String getUserId() {
return userIdHolder.get();
}
public static void remove() {
userIdHolder.remove();
}
}
也可以直接使用 org.slf4j.MDC,其内部也是通过 ThreadLocal 来实现的。
(2)在拦截器 preHandle 方法中从请求头中获取 userId 设置到 ThreadLocal 变量中,
afterCompletion 中回收 ThreadLocal。
@Component
public class CustomInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
CustomContext.setUserId(request.getHeader("userId"));
...
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
CustomContext.remove();
}
}
(3)在接口处理线程的任何地方可以通过调用 CustomContext.getUserId() 获取到请求的 userId。
注意点:
- 通过 ThreadLocal.withInitial 设置初始值,避免空指针
- 调用 remove() 方法回收自定义的 ThreadLocal 变量
阿里 Java 开发手册:
- ThreadLocal 对象使用 static 修饰
阿里 Java 开发手册:
原理分析
ThreadLocalMap 的 key 使用 ThreadLocal 弱引用 WeakReference 的原因:
指向 ThreadLocal 的强引用在方法执行完后会被回收后,只有 ThreadLocalMap 的 key 这个弱引用指向 ThreadLocal 对象,就可以使 ThreadLocal 对象在方法执行完后顺利被回收且 Entry 的 key 引用指向为 null。
但这样不能回收 value 对象,需要 ThreadLocalMap 调用 get、set 方法时发现 key 为 null 时才会回收 value 和 entry。所以必须在不使用某个 ThreadLocal 对象后,手动调用 remove 方法来删除它。
四种引用
- 强引用
最常见的普通引用,如果没有其他的引用关系,只要超过了这个强引用的作用域(方法的局部变量指向对象,方法执行完后就超过了局部变量的作用域)或者显示的将强引用赋值为 null ,一般认为就是可以被垃圾回收的。
package juc.threadlocal;
class MyObject {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize method invoked");
}
}
public class ReferenceDemo {
public static void main(String[] args) {
print();
// print 方法执行完后,指向对象的强引用也就是局部变量object被回收,没有指向对象的强引用,执行 GC 时就可以被回收
System.gc();
System.out.println("gc after ");
}
private static void print() {
MyObject object = new MyObject();
System.out.println("gc before " + object);
}
}
- 软引用
对于只有软应用的对象来说,当系统内存充足时,它不会被回收,当系统内存不足时,它会被回收。
package juc.threadlocal;
import java.lang.ref.SoftReference;
class MyObject {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize method invoked");
}
}
public class ReferenceDemo {
public static void main(String[] args) {
SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());
System.out.println(softReference.get());
}
}
- 弱引用
对于只有软应用的对象来说,不管系统内存释放充足,它都会被回收。
package juc.threadlocal;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
class MyObject {
@Override
protected void finalize() throws Throwable {
System.out.println("finalize method invoked");
}
}
public class ReferenceDemo {
public static void main(String[] args) {
WeakReference<MyObject> weakReference = new WeakReference<>(new MyObject());
System.out.println(weakReference.get());
}
}
- 虚引用
package juc.threadlocal;
import java.lang.ref.*;
class MyObject {
}
public class ReferenceDemo {
public static void main(String[] args) {
MyObject myObject = new MyObject();
ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
PhantomReference<MyObject> phantomReference = new PhantomReference<>(myObject, referenceQueue);
new Thread(() -> {
while (true) {
Reference<? extends MyObject> reference = referenceQueue.poll();
if (reference != null) {
System.out.println("有虚对象加入了队列");
break;
}
}
}).start();
myObject = null;
System.gc();
}
}
使用 PhantomReference 时,MyObject 类不能覆盖 finalize() 方法。
参考
- 尚硅谷JUC并发编程 (https://www.bilibili.com/video/BV1ar4y1x727?spm_id_from=333.788.videopod.episodes&vd_source=9266b9af652d5902d068c94b9d60116f&p=100)
原文地址:https://blog.csdn.net/kwok924/article/details/143692991
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!