我正在尝试将值缓存在ConcurrentHashMap
in the Session
。为了避免竞争条件并确保在任何线程尝试使用我的地图之前创建它,我使用HttpSessionListener.sessionCreated()
将地图添加到Session
:
@Override
public void sessionCreated(HttpSessionEvent event) {
event.getSession()
.setAttribute(MY_CACHE_KEY, new ConcurrentHashMap());
}
这段代码是否保证在任何其他线程访问会话之前完成(通过request.getSession()
例如)?
我看了看HttpSessionListener JavaDoc https://javaee.github.io/javaee-spec/javadocs/javax/servlet/http/HttpSessionListener.html#sessionCreated-javax.servlet.http.HttpSessionEvent-和Servlet 4.0 规范 https://download.oracle.com/otndocs/jcp/servlet-4-final-eval-spec/index.html而且线程安全似乎没有任何保证。
Serlvet 规范。多次引用会话线程安全性,但据我了解,这些引用都与会话侦听器和会话创建相关:
7.7.1 线程问题
执行请求线程的多个 Servlet 可能会同时主动访问同一会话对象。容器必须确保对表示会话属性的内部数据结构进行操作
以线程安全的方式执行。开发人员有责任对属性对象本身进行线程安全访问。这将保护 HttpSession 对象内部的属性集合免受并发访问,
消除应用程序导致该集合损坏的机会。除非在规范中的其他地方明确说明(例如,第 7.7.1 节,第 7-67 页上的会话对象的“线程问题”),否则必须假定从请求或响应中提供的对象是非线程安全的。这包括但不限于从返回的 PrintWriter
ServletResponse.getWriter() 和从 ServletResponse.getOutputStream() 返回的 OutputStream。
11.5 监听器实例和线程
在开始执行应用程序中的第一个请求之前,容器需要完成 Web 应用程序中侦听器类的实例化。容器必须维护对每个侦听器实例的引用,直到为 Web 应用程序提供最后一个请求为止。
ServletContext 和 HttpSession 对象的属性更改可能会同时发生。容器不需要将生成的通知同步到属性侦听器类。维护状态的侦听器类负责数据的完整性,并且应该显式处理这种情况。
似乎很明显sessionCreated()
必须在线程访问会话之前完成,但是“明显正确的代码”以前对于多线程来说是不安全的 https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html.
这种歧义不存在于ServletContextLister.contextInitialized() https://javaee.github.io/javaee-spec/javadocs/javax/servlet/ServletContextListener.html#contextInitialized-javax.servlet.ServletContextEvent-因为它保证在之前完成Servlet
初始化和Servlet.init()保证是单线程的并且发生在任何请求之前 https://javaee.github.io/javaee-spec/javadocs/javax/servlet/Servlet.html#init-javax.servlet.ServletConfig-.
我至少测试过 Tomcat,它确实确保了sessionCreated()
之前完成request.getSession()
返回。我通过放置断点进行测试sessionCreated()
并发送一个请求request.getSession()
。直到我从断点继续执行该请求才完成。然而,一Servlet
容器实现的行为并不能真正证明所有容器/服务器都以这种方式运行。