Java中的Servlet:实例、会话与多线程——深度解析

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么Servlet至今仍让人困惑,以及如何应对?

\n\n

如果你曾经在Stack Overflow上寻找过关于“Servlet是如何工作的?”这个问题的答案,那么你很可能遇到过那个拥有数千次浏览的经典帖子。那里反复被提出的问题是:“会创建多少个Servlet实例?如何在请求之间共享数据?为什么我的变量表现得像个疯子?”

\n\n

老实说:Servlet并不是什么高深莫测的技术,但其中确实有不少陷阱。尤其是当涉及到多线程和共享变量的时候。在本文中,我们不仅仅是复述文档。我们将采用一个真实的例子,对其进行深入剖析,并展示如何避免踩坑。开始吧!

\n\n

Servlet如何进入内存:实例化与生命周期

\n\n

让我们从简单的问题开始:会创建多少个Servlet实例?答案是:一个。是的,你没听错。Servlet容器(如Tomcat、Jetty等)为整个应用程序只创建一个Servlet实例。这个实例会一直存在,直到应用程序被卸载。

\n\n

典型的生命周期如下所示:

\n\n
    \n
  • init() — 在Servlet加载时调用一次。在这里初始化数据库连接、配置等。
  • \n
  • service() — 为每个传入的请求调用。这里进行实际的请求处理。
  • \n
  • destroy() — 在Servlet卸载前调用。清理资源。
  • \n
\n\n

关键点在于:service()(以及它的派生方法doGet、doPost)会在每个请求的独立线程中执行。也就是说,同一个Servlet实例同时处理数百个请求。而有趣的事情就从这里开始。

\n\n

示例:一个Servlet,多个线程

\n\n
@WebServlet("/counter")\npublic class CounterServlet extends HttpServlet {\n    private int counter = 0;  // 共享变量!\n\n    @Override\n    protected void doGet(HttpServletRequest req, HttpServletResponse resp) \n            throws ServletException, IOException {\n        counter++;\n        resp.getWriter().write("Counter: " + counter);\n    }\n}
\n\n

如果你运行这段代码并发送10个并行请求,你不会按顺序得到从1到10的数字。相反,你会看到类似这样的结果:2, 5, 3, 8, 1... 或者counter可能会跳过某些值。为什么?因为counter++不是一个原子操作。两个线程可能读取到相同的值,各自递增后再写回,从而导致一次递增丢失。

\n\n

结论:Servlet实例的字段是共享变量。它们对所有线程可见。如果你不同步对它们的访问,就会发生竞态条件。

\n\n

会话:如何不混淆用户

\n\n

现在想象一下:你想要存储每个用户独有的数据(购物车、登录信息、设置等)。为此,Java Servlets提供了HttpSession。会话会为每个新用户创建,并一直存在,直到用户关闭浏览器或超时。

\n\n

会话是非共享内存。每个用户获得自己独立的会话实例。但在会话内部,你可以存储任何对象,这些对象仅对该用户可用。

\n\n

示例:使用会话

\n\n
@WebServlet("/session-demo")\npublic class SessionServlet extends HttpServlet {\n    @Override\n    protected void doGet(HttpServletRequest req, HttpServletResponse resp) \n            throws ServletException, IOException {\n        HttpSession session = req.getSession();\n        Integer visitCount = (Integer) session.getAttribute("visitCount");\n        if (visitCount == null) {\n            visitCount = 0;\n                

博客

书籍推荐