ThreadLocal 的开发应用
作者:KerryWu
来源:SegmentFault 思否社区
ThreadLocal是线程私有的局部变量存储容器,可以理解成每个线程都有自己专属的存储容器,用来存储线程私有变量。ThreadLocal 在日常开发框架中应用广泛,但用不好也会出现各种问题,本文就此讲解一下。
1. 应用场景
ThreadLocal 的常见应用场景有两种:
多线程并发场景中,用来保障线程安全。 处理较为复杂的业务时,使用ThreadLocal代替参数的显示传递。
1.1. 保障线程安全
1.2. 显示传递参数
public class UserProfileThread {
private static ThreadLocal<UserProfile> USER_PROFILE_TL =new ThreadLocal<>();
public static void setUserProfile(UserProfile userProfile){
USER_PROFILE_TL.set(userProfile);
}
public static UserProfile getUserProfile() {
return USER_PROFILE_TL.get();
}
public static String getCurrentUser() {
return Optional.ofNullable(USER_PROFILE_TL.get())
.map(UserProfile::getUid)
.orElse(UserProfile.ANONYMOUS_USER);
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
UserProfile userProfile = null;
// ... 验证和获取用户信息 userProfile
UserProfileThread.setUserProfile(userProfile);
filterChain.doFilter(servletRequest, servletResponse);
}
//获取当前用户
String uid=UserProfileThread.getCurrentUser();
//获取当前用户对象
UserProfile user=UserProfileThread.getUserProfile();
2. 实现原理
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// 省略其他方法
}
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
//省略其他
}
每个线程是一个Thread实例,其内部维护一个threadLocals的实例成员,其类型是ThreadLocal.ThreadLocalMap。 ThreadLocal本身并不是一个容器,我们存取的value实际上存储在ThreadLocalMap中,ThreadLocal只是作为TheadLocalMap的key。
3. 注意事项
线程复用:在“获取接口的当前请求用户”的例子中,Tomcat中是通过线程池来处理用户请求的,而线程池中线程是复用的。肯定会出现一个线程前后被不同用户的接口请求复用的情况,因此需要对用过的ThreaLocal变量进行覆盖或清除。 内存溢出:由于ThreadLocalMap的生命周期跟Thread一样长,如果创建的ThreadLocal变量很多,即对应的key占用的内存很大,但却没有手动删除,到了一定程度就会导致内存泄漏。
评论