我正在调试一些有很多异常的旧servlet。由于有很多(太多)同步的关键字,所以没有并发修改异常,但我仍然怀疑servlet线程不安全。我读到了这个关于servlet和线程安全的非常有趣的问题,并认为这个例子是一个很好的基础:
public class ExampleServlet extends HttpServlet {
private Object thisIsNOTThreadSafe;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Object thisIsThreadSafe;
thisIsNOTThreadSafe = request.getParameter("foo"); // BAD!! Shared among all requests!
thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe.
}
}
事实上,那些为我的servlet编码的人似乎也意识到了这一点,但他们决定通过这样做来绕过它:
public class ExampleServlet extends HttpServlet {
private ThreadLocal<MyObject> thisIsMaybeThreadSafe = new ThreadLocal<MyObject>();;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// we want to avoid having to use this parameter in every method
thisIsMaybeThreadSafe.set((MyObject)getObjectInSesssion("foo"));
doStuff(request, response);
}
}
代码中还包含之类的内容
synchronized(request.getAttribute("foo")){
doStuff(request, response);
}
我对这一切有一种不好的感觉,我一直在寻找这是危险的证据。事实上,在阅读了设置属性时出现的NullPointerException问题后,我感觉一定出了问题,因为我得到了类似的堆栈:
11:07:17,525 ERROR [com.mycompany.myproject.web.business.servlet.map.tree.MapServlet] Error processing AjaxTreeAccessRequest
java.lang.NullPointerException
at org.apache.catalina.connector.Request.notifyAttributeAssigned(Request.java:1493)
at org.apache.catalina.connector.Request.setAttribute(Request.java:1484)
at org.apache.catalina.connector.RequestFacade.setAttribute(RequestFacade.java:539)
at javax.servlet.ServletRequestWrapper.setAttribute(ServletRequestWrapper.java:244)
at org.apache.myfaces.context.servlet.RequestMap.setAttribute(RequestMap.java:51)
at org.apache.myfaces.util.AbstractAttributeMap.put(AbstractAttributeMap.java:108)
at org.apache.myfaces.el.VariableResolverImpl.resolveVariable(VariableResolverImpl.java:304)
at org.springframework.web.jsf.DelegatingVariableResolver.resolveOriginal(DelegatingVariableResolver.java:120)
at org.springframework.web.jsf.DelegatingVariableResolver.resolveVariable(DelegatingVariableResolver.java:105)
at com.mycompany.myproject.web.common.servlet.AbstractFacesServlet.getManagedBean(AbstractFacesServlet.java:67)
at com.mycompany.myproject.web.business.servlet.map.tree.MapServlet.getSessionTreeBean(MapServlet.java:184)
at com.mycompany.myproject.web.business.servlet.map.tree.AjaxTreeAccess.initRequest(AjaxTreeAccess.java:355)
at com.mycompany.myproject.web.business.servlet.map.tree.AjaxTreeAccess.processRequest(AjaxTreeAccess.java:134)
at com.mycompany.myproject.web.common.servlet.AbstractFacesServlet.handleRequest(AbstractFacesServlet.java:81)
at org.springframework.web.context.support.HttpRequestHandlerServlet.service(HttpRequestHandlerServlet.java:63)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.orm.hibernate3.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:198)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.mycompany.myproject.commun.presentation.jsf.OpenSynchronizedSessionInViewFilter.doFilterInternal(OpenSynchronizedSessionInViewFilter.java:58)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:265)
at com.mycompany.myproject.web.common.filter.SwitchUserProcessingFilter.doFilter(SwitchUserProcessingFilter.java:66)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:275)
at org.acegisecurity.ui.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:110)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:275)
at org.acegisecurity.providers.anonymous.AnonymousProcessingFilter.doFilter(AnonymousProcessingFilter.java:125)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:275)
at org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:81)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:275)
at org.acegisecurity.ui.AbstractProcessingFilter.doFilter(AbstractProcessingFilter.java:229)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:275)
at org.acegisecurity.ui.logout.LogoutFilter.doFilter(LogoutFilter.java:106)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:275)
at org.acegisecurity.context.HttpSessionContextIntegrationFilter.doFilter(HttpSessionContextIntegrationFilter.java:286)
at org.acegisecurity.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:275)
at org.acegisecurity.util.FilterChainProxy.doFilter(FilterChainProxy.java:149)
at org.acegisecurity.util.FilterToBeanProxy.doFilter(FilterToBeanProxy.java:98)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.mycompany.myproject.web.business.filter.UserBindingFilter.doFilter(UtilisateurCourantBindingFilter.java:55)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:236)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.mycompany.myproject.web.common.filter.SessionTimeoutFilter.doFilter(SessionTimeoutFilter.java:67)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:236)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.mycompany.myproject.web.common.filter.SessionLoginFilter.doFilter(SessionLoginFilter.java:56)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.mycompany.profiling.prof.filter.ProfContextFilter.doFilter(ProfContextFilter.java:26)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:136)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:526)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1078)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:655)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1566)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1523)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:744)
我宁愿去掉所有这些ThreadLocal的东西,但在一个没有人真正记得它是如何工作的旧遗留代码中,重构将是巨大而危险的,所以我需要认真的建议。
仅供参考整个应用程序都在遗留的spring-JSF代码中,一些ThreadLocal变量实际上是与JSF会话相关的bean。我应该怎么做才能检查此应用程序的全局线程安全性?
synchronized(request.getAttribute("foo"))
是坏的,因为请求中不可能有foo
,您将得到NPE。最好使用一些专用的锁定对象- 关于
ThreadLocal
的使用——如果不过度使用也没关系。对于您发布的大小的代码来说,这是可以的,但我认为真正的问题在于您的真实代码库,除了声明您需要为您的真实逻辑(而不是与Servlet API和并发相关的逻辑)编写大量单元测试(尽可能简单),然后逐渐将代码库重构到更理智的状态之外,在这里几乎不可能给出简短的有用建议
希望这有帮助:)
在我看来,这个用于会话处理的ThreadLocal是一个危险的解决方案。
ThreadLocal将为调用该类的每个线程创建一个MyObject。不是每个客户。
我非常确信,servlet容器不会为每个传入连接创建一个新的线程,而且我也确信,您不能确保每次都由同一个线程处理您的请求。
我可能对servlet容器的工作方式错了(如果我错了,请告诉我),但如果我是你,我会创建大量并发测试,以确保每个客户端的会话都有一个单独的MyObject。
如果它工作,这意味着在重负载下,每个传入的连接都将创建一个对象。如果您想使应用程序具有可扩展性,那就太糟糕了。
所以我认为基本上的问题是,在这个代码示例中,传入连接与调用doGet的线程混淆了。你觉得怎么样