面试题答案
一键面试工作原理区别
- ThreadLocal:每个线程都有一个自己的
ThreadLocalMap
,ThreadLocal
实例作为键,其对应的值存放在调用线程的ThreadLocalMap
中。当通过ThreadLocal
的get()
方法获取值时,实际上是从当前线程的ThreadLocalMap
中取值,从而实现线程间的数据隔离。例如,每个线程有自己独立的数据库连接,通过ThreadLocal
来管理,避免多线程竞争。 - InheritableThreadLocal:它是
ThreadLocal
的子类,在创建子线程时,子线程会从父线程中继承InheritableThreadLocal
的值。具体实现是在Thread
类创建时,会将父线程的InheritableThreadLocal
的ThreadLocalMap
复制一份到子线程中。比如在一个Web应用中,父线程持有用户的登录信息,子线程可能需要在后续的任务中使用这些信息,就可以通过InheritableThreadLocal
来传递。
使用场景区别
- ThreadLocal:适用于每个线程需要独立保存一份数据,且不需要在父子线程间传递数据的场景。如在多线程处理任务时,每个线程需要维护自己的计数器,各线程的计数器互不干扰。
- InheritableThreadLocal:适用于需要在父子线程间传递数据的场景。比如在分布式系统中,父线程发起一个请求,子线程需要携带父线程的一些上下文信息(如traceId用于链路追踪)进行后续的处理。
综合运用场景举例
假设在一个电商系统中,有一个订单处理流程。主线程负责接收订单请求,首先从请求中获取用户的登录信息(如用户ID、用户名等),并将这些信息存入InheritableThreadLocal
,因为后续可能会创建子线程进行订单的各种处理(如库存检查、物流信息获取等),这些子线程需要用户信息来进行权限验证等操作。
同时,在订单处理过程中,每个子线程可能需要记录自己独立的操作日志,此时可以使用ThreadLocal
来存放每个子线程的日志记录器实例。这样既保证了父子线程间关键信息的传递,又保证了每个子线程有自己独立的日志记录空间,不会相互干扰。
示例代码如下:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalExample {
private static final InheritableThreadLocal<String> userInfo = new InheritableThreadLocal<>();
private static final ThreadLocal<StringBuilder> threadLog = new ThreadLocal<>();
public static void main(String[] args) {
userInfo.set("User1:123456");
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(() -> {
threadLog.set(new StringBuilder());
threadLog.get().append("子线程1开始处理订单,用户信息: ").append(userInfo.get());
// 模拟订单处理操作
System.out.println(threadLog.get().toString());
});
executorService.submit(() -> {
threadLog.set(new StringBuilder());
threadLog.get().append("子线程2开始处理订单,用户信息: ").append(userInfo.get());
// 模拟订单处理操作
System.out.println(threadLog.get().toString());
});
executorService.shutdown();
}
}
在上述代码中,InheritableThreadLocal
用于父子线程间传递用户信息,ThreadLocal
用于每个子线程独立记录日志。