我们曾经做过一个多租户的项目,每个租户用一个唯一的平台号。程序中有的地方把当前用户的平台号放在一个ThreadLocal变量中。部署到tomcat上后发现一个很奇怪的现象:有时候用户的平台号会出现错乱,即一个人的平台号会出现在另一个登录用户的身上。最后发现问题出在线程池上。
大家知道ThreadLocal中的值实际上保存在线程对象中。在有线程池的情况下,线程用完后会还到线程池中,下一次需要线程时就从线程池中获取一个。如果ThreadLocal中的值上次没清空,这个值就会保留下来继续被使用。就是这个原因导致ThreadLocal中的数据混乱的。
原因清楚了,解决方法也就很简单了。ThreadLocal使用完后把值清除即可。
为简化问题,可以写一个ThreadLocalRegistry负责记住所有需要清除的ThreadLocal,然后在ThreadLocalCleanerInterceptor中统一清除。代码如下:
public class ThreadLocalRegistry {
private static ThreadLocal<List<ThreadLocal>> threadLocals = new ThreadLocal<List<ThreadLocal>>();
public static void register(ThreadLocal threadLocal) {
synchronized (threadLocals) {
if (threadLocals.get() == null) {
threadLocals.set(new ArrayList<ThreadLocal>());
}
threadLocals.get().add(threadLocal);
}
}
public static void clear() {
synchronized (threadLocals) {
if (threadLocals.get() == null) {
return;
}
for (ThreadLocal threadLocal : threadLocals.get()) {
threadLocal.remove();
}
threadLocals.get().clear();
threadLocals.remove();
Guzz.setDBGroup(null);
Guzz.setTableCondition(null);
}
}
}
public class ThreadLocalCleanerInterceptor implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(req, res);
} finally {
ThreadLocalRegistry.clear();
}
}
public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} finally {
ThreadLocalRegistry.clear();
}
}
@Override
public void init(FilterConfig config) throws ServletException {
}
}