如果我在HttpServlet#init(ServletConfig)中分配实例字段,Servlet规范是否保证我可以在doGet()中读取它们?
如果我在HttpServlet#init(ServletConfig)中分配实例字段,Servlet规范是否保证我可以在doGet()中读取它们?
Servlet规范规定,容器将实例化一个java.servlet.HttpServlet
的单一实例,并从多个工作线程中调用服务方法(doGet()
/doPost()
)。\n根据正常的线程规则,在init(ServeltConfig)
中对实例级别字段进行赋值,并不能保证在其他执行doGet()
的线程读取这些字段之前“发生”,除非有人在某个时刻进行同步。\n可能,容器确实会进行某种外部同步,以确保在init()
中完成的工作对“后续”线程可见。\n然而,Servlet规范是否明确保证我是线程安全的?我刚才没有找到这样的保证,尽管我必须承认,自Servlet 2.4以来,我还没有从头到尾阅读过规范。\n编辑\n例如,由于一些回答者搞混了一些事情,我的问题是:Servlet规范中的哪一部分说明了以下类是线程安全的?\n
@WebServlet (initParams = {@WebInitParam(name="b", value="true")}) public Decider extends HttpServlet { private boolean b = false; public void init(ServletConfig config) { this.b = Boolean.parseBoolean(config.getAttribute("b")); } public void doGet(HttpServletRequest req, HttpServletResponse res) { res.sendRedirect(b ? "/true" ? "/false"); } }
\n当然,如果我这样做:\n
public static void main(String[] argv) { HttpServlet s = new Decider(); Thread t1 = new Thread(new Runnable() { public void run() { s.init(...); } }); Thread t2 = new Thread(new Runnable() { public void run() { s.doGet(...); } }); t1.start(); t2.start(); }
\n...那么我将有一个线程错误。容器有什么不同呢?\n编辑2\n所有声称“容器会处理这个问题”的答案当然是受欢迎的,但我的问题特别是关于Servlet规范是否保证了这种行为。要充分回答这个问题,你必须参考Servlet规范。(任何版本都可以,我没问题)。
问题的出现原因是在Servlet中,如果在init()方法中给实例字段赋值,能否在doGet()方法中读取它们。解决方法是查看Servlet规范文档来了解Servlet的生命周期方法调用顺序,以及容器必须按照规范来实现这些方法。Servlet规范规定了容器必须在处理请求之前先完成所有的初始化操作,因此在init()方法中给实例字段赋值是安全的。下面是根据提供的内容整理的文章:
在Servlet中,如果在init()方法中给实例字段赋值,能否在doGet()方法中读取它们?这个问题的出现源于对Servlet的生命周期方法调用顺序的疑问。解决方法是查看Servlet规范文档来了解Servlet的生命周期方法调用顺序,以及容器必须按照规范来实现这些方法。
在给定的例子中,init()方法和doGet()方法可能会重叠。但是在Servlet容器中,这是不可能发生的。容器会在开始处理请求之前完成所有的初始化操作。因此,在这两个方法之间不存在多线程问题。
然而,在doXXX()方法中使用共享数据仍然会存在问题。
要真正支持Servlet规范,容器必须遵循规范规定的顺序执行这些方法。
Servlet的生命周期在Servlet 3.0规范文档的第2.3章节中进行了描述。文档中指出,在Servlet对象实例化之后,容器必须在处理客户端请求之前初始化Servlet。初始化过程用于读取持久化配置数据、初始化昂贵的资源(如基于JDBC的连接)以及执行其他一次性活动。容器通过调用Servlet接口的init()方法来初始化Servlet实例。
通过查看Servlet规范文档,我们可以确定容器必须按照规范来执行这些方法。
因此,要回答这个问题,只需要指出规范规定了容器必须按照规定的顺序调用这些方法即可。
Servlet规范是您能够得到的最好的答案。
问题的出现原因是在Servlet规范中,它明确规定servlet容器在实例化servlet后会调用init方法,并且在servlet能够接收任何请求之前,init方法必须成功完成。此外,根据servlet的生命周期,servlet应该在从多个线程接收服务请求之前进行初始化。
然而,问题在于init()方法将在Web应用程序启动时由某个线程调用,而doGet()方法将在服务器的线程池中的任何其他线程中调用。那么,谁负责确保实例字段的安全发布?
对此问题,有一个回答是通过在init方法中将实例字段标记为volatile。通过这种方式,可以假设GenericServlet是根据此方式实现的。
从规范的角度来看,我认为"complete successfully *before*"可以理解为正式的"happens-before"关系。