关于SprimgMVC中request的线程安全

来源:互联网 发布:安卓编程教程 编辑:程序博客网 时间:2024/06/05 01:16

我们知道springmvc中request是方法级别的,一个方法对应一个request。那么如果我们把request设置为类级别的变量呢?就像这样:

@Controller@RequestMapping("/admin")public class AdminController {    private HttpServletRequest tempRequest;    @ModelAttribute    public void getRequest(HttpServletRequest request){        // 每个方法执行前都将当前的request赋给tempRequest        tempRequest = request;    }    ......}

根据Sprimg的默认规则,controller对于factorybean默认是单例,controller只有一个。我们知道tomcat对于每一个请求都会生成一个处理线程去处理当前请求,那么在并发量高的情况下,我们都去请求例如:public ModelAndView checkAdmin() 这样一个方法,因为我们设置request为类变量,那么势必会造成后一个request覆盖了前一个request的一些请求参数。如果下面这个例子:

private HttpServletRequest tempRequest;    @ModelAttribute    public void getRequest(HttpServletRequest request){        // 每个方法执行前都将当前的request赋给tempRequest        tempRequest = request;    }    @RequestMapping("/checkAdmin")    public void checkAdmin() {        try{            // 这里让请求线程等待10秒,模拟高并发            Thread.currentThread().sleep(10000);        }catch(InterruptedException e){}        String num = tempRequest.getParameter("num");        System.out.println("请求" + tempRequest.hashCode() + ", num = " + num);    }

然后我们在10秒内分别请求:

http://localhost:8081/eHow/admin/checkAdmin?num=1http://localhost:8081/eHow/admin/checkAdmin?num=2http://localhost:8081/eHow/admin/checkAdmin?num=3

先看结果:

请求217031088, num = 3请求217031088, num = 3请求217031088, num = 3

照道理,应该request 的hashcode(内存地址)不一样,num也不一样。但是通过结果可以发现,虽然说每次请求num都不一样,但是打印结果确是request hashcode相同,num也一样。这是因为我们让请求线程睡眠了10s,10s后再去从tempRequest取值,那么其实tempRequest已经被最后一个覆盖,三次请求其实最后取的都是同一个tempRequest。也就是说,这样设计的request是线程不安全的,并不是将当前request放在当前线程threadlocal中。(threadlocal简单理解就是将一些值与当前线程关联起来,放的时候放在当前线程中,取得时候就从当前线程中去取。)

ok,那么我们试一下,将每次请求的request放在threadlocal中。

    // private HttpServletRequest tempRequest;    // 这个threadlocal就是放当前线程的HttpServletRequest变量    private static ThreadLocal<HttpServletRequest> local  = new ThreadLocal<HttpServletRequest>();    @ModelAttribute    public void getRequest(HttpServletRequest request){        // 将当前request放入threadlocal中        local.set(request);    }    @RequestMapping("/checkAdmin")    public void checkAdmin() {        try{            Thread.currentThread().sleep(10000);        }catch(InterruptedException e){}        // 取的时候从当前的线程中去取request        HttpServletRequest request = local.get();        String num = request.getParameter("num");        System.out.println("请求" + request.hashCode() + ", num = " + num);    }

请求地址还是和上面一样,看一下结果:

请求2114778512, num = 1请求693198960, num = 2请求973234962, num = 3

可以看到,hashcode不一样,num也不同,也就是说确实将每次请求的request放在了当前的线程中,取得时候也可以从当前线程中取得,这样就不存在覆盖的问题,所以说这种方式是线程安全的。

spring应该是考虑到了这一点,所以给我们提供了一个HttpServletRequest的代理bean,通过@AutoWired注入。具体的实现方式其实和我们上面threadlocal差不多。因为controller是单例,所以我们注入一个httpservletrequest的代理bean,当有一个新的请求进来时,就判断当前线程中有没有httpservletrequest对象,没有就在threadlocal中放一个httpservletrequest对象,当我们去取request的一些参数时,代理bean就从当前线程中的httpservletrequest去取。这样就保证了线程安全。
我们试一下:

    // 注入代理类    @Autowired    private HttpServletRequest tempRequest;    // 这个threadlocal就是放当前线程的HttpServletRequest变量    // private static ThreadLocal<HttpServletRequest> local  = new ThreadLocal<HttpServletRequest>();    //  @ModelAttribute    //  public void getRequest(HttpServletRequest request){    //      // 将当前request放入threadlocal中    //      local.set(request);    //  }    @RequestMapping("/checkAdmin")    public void checkAdmin() {        try{            Thread.currentThread().sleep(10000);        }catch(InterruptedException e){}        String num = tempRequest.getParameter("num");        System.out.println("请求" + tempRequest.hashCode() + ", num = " + num);    }

请求地址还是和上面一样,看一下结果:

请求368958259, num = 1请求368958259, num = 2请求368958259, num = 3

咦,为什么hashcode一样,num不一样,跟上面结果不同?我们刚才说到request在去取参数也就是tempRequest.getParameter(“num”) 的时候代理bean会从当前threadlocal去取request。
我们贴一部分这个代理bean相关的源码,看一下原因:

 // 这个就是代理的request bean private final ObjectFactory objectFactory;   public ObjectFactoryDelegatingInvocationHandler(ObjectFactory objectFactory) {       this.objectFactory = objectFactory;   }   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {              if (methodName.equals("equals")) {                 return (proxy == args[0]);              }              else if (methodName.equals("hashCode")) {                 // 可以看到当方法为hashcode的时候,就会取出代理的hashcode                // 所以hashcode都是一样的                return System.identityHashCode(proxy);              }              else if (methodName.equals("toString")) {                  return this.objectFactory.toString();              }              try {                  // 如果方法是其他                // ObjectFactory的getObjcect就是从当前threadlocal中取数据                return method.invoke(this.objectFactory.getObject(), args);              }              catch (InvocationTargetException ex) {                  throw ex.getTargetException();              }          }  
private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {          public ServletRequest getObject() {              // currentReuqestAttributes负责从Threadlocal中获取对象            return currentRequestAttributes().getRequest();          }          @Override          public String toString() {              return "Current HttpServletRequest";          }      }  

ok,也就是说除了equals、hashcode以及toString以外的其他方法都是从threadlocal中取。所以才会造成上面hashcode一样,num不一样的情况。所以这样也是线程安全的。

还有一种线程安全,就是方法级别的reqeust。

    // 注入代理类    //  @Autowired    //  private HttpServletRequest tempRequest;    // 这个threadlocal就是放当前线程的HttpServletRequest变量    // private static ThreadLocal<HttpServletRequest> local  = new ThreadLocal<HttpServletRequest>();    //  @ModelAttribute    //  public void getRequest(HttpServletRequest request){    //      // 将当前request放入threadlocal中    //      local.set(request);    //  }    @RequestMapping("/checkAdmin")    public void checkAdmin(HttpServletRequest request){        try{            Thread.currentThread().sleep(10000);        }catch(InterruptedException e){}        String num = request.getParameter("num");        System.out.println("请求" + request.hashCode() + ", num = " + num);    }

看结果:

请求1814088687, num = 1请求2071623777, num = 2请求1324577120, num = 3

可以看到每次输出也是不同的。因为每次请求都是一个不同的request,每个request都是方法级别的,独享的,也就不存在前面覆不覆盖的问题了。

以上就是对springmvc request线程安全的一些测试。

原创粉丝点击