Posted on 2010-09-10 13:44
xiaolang 阅读(5629)
评论(0) 编辑 收藏
首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象
的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。
个人版中有一个比较好的应用场景,就是为会员查询服务做的catche。
在用户的一次请求中需要多次查询用户的卡信息,会多次去调用远程服务,造成性能上的浪费。如果能在本地做一个简单的cache,就可以省去远程调用的开销,就可以如下图所示
从图中可以看出,优化后的将一部分cache存在本地,就省去了远程调用的开销,从一定程度上减轻了会员核心的压力。
Cache一般都有一个刷新时间的问题,时间长了,信息可能不准确,时间短了,比较耗费性能。最好的情况是这个cache过期了,不需要使用了,立即把清除,废弃,这个最理想的情况。
大家应该知道,用户从发起请求,到服务器响应的这个过程中,在服务器中是在一个线程中的。如果我们吧查询出来的东西放到这个线程中,到用户请求结束时,把这些东西清理掉,应该是一个不错的cache方案。
接下来的问题就简单了,我们只需要把查询的东西放到threadLocal就可以了。
看大牛李磊如何实现这一点
1. 首先需要初始化一个threadLocal
/** 用于将会员信息保存在本地的线程变量*/
private static
ThreadLocal<List<UserInfo>>
localUserInfo = new
ThreadLocal<List<UserInfo>>() {
protected synchronized List<UserInfo> initialValue() {
return new ArrayList<UserInfo>();
} };
2. 将查询出来的东西放到threadLocal,下次查询的时候,就可以先到threadLocal中取,如果没有,再调用远程服务
UserInfo userInfo = getFromLocalByCardNo(cardNo);
if (userInfo == null) {
userInfo =
userInfoQueryService.findUserInfo(cardNo);
putToLocal(userInfo);
}
3.用户的请求结束,把这些内容清理掉。
为什么要把内容清理掉
如果一直存放在threadLocal,
1是比较耗费内存;
2是里面的内容可能是过期的(用户修改了信息,threadLocal里面没有更新)
3请求结束后需要清除ThreadLocal的一个很重要的原因就是,如果使用了线程池,在请求结束后,线程的生命周期还没有结束,而是放回到池中,这样下次再使用此线程的时候就会获得上次的上下文了。
我们如何做到这一点呢?
开始之前先给大家介绍一个接口,spring mvc中的。HandlerInterceptor
这个接口中有三个方法
preHandle
在一个该方法会在Controller的方法执行前会被调用,可以使用这个方法来中断或者继续执行链的处理,当返回true时,处理执行链会继续,当返回false时,则不会去执行Controller的方法
postHandle
这 3个方法会在在controller的方法执行之后,在DispatcherServlet类导向到view进行render之前依次执行。最有用的是使 用postHandleRender方法,因为它有ModelAndView 传进来,那么我们就可以在render view之前往view中添加额外的model对象,或者对view的去处进行修改(例如下面的“用HTML还是用Excel来作为View ”例子就是对view进行了更改)
afterCompletion
该方法会在render view完成后执行,也可以说在请求过程(request processing)完成之后执行。该方法可以用来清理资源(例如象blackboard
building block release context)
我们只需要在afterCompletion中把本次线程中的存放的信息清理掉,就可以了。
大家应该记得这一段代码,初始化threadLocal并不需要这一点,
static {
ThreadLocalCleaner.register(localUserInfo);
ThreadLocalCleaner.register(localCertifyStatus);
}
让我们先看下ThreadLocalCleaner的代码
/**
* 清理所有的线程变量。
*/
public static void clear() {
for (ThreadLocal<?> tl : tls)
{
tl.remove();
}
}
/**
* @param tl
*/
public static void register(ThreadLocal<?> tl) {
tls.add(tl);
}
方 法register将初始化的ThreadLocal放到一个总的ThreadLocal里,方法clear将总的ThreadLocal里面的内容清 空。我们只需要在HandlerInterceptor实现类中的afterCompletion方法中把clear调用一下就可以了。