应使用 AtomicInteger 存入 ServletContext:contextInitialized 中 setAttribute("onlineCount", new AtomicInteger(0));sessionCreated 增、sessionDestroyed 减(须判空);前端通过受控接口读取,集群下不适用。

ServletContextListener 初始化时怎么安全设置在线人数计数器
不能直接在 contextInitialized 里 new 一个普通 int 或 Integer 当全局计数器——它不是线程安全的,多个请求并发进来会丢数。必须用原子类或加锁机制。
推荐用 AtomicInteger 存到 ServletContext 中,这是最轻量且线程安全的做法:
public void contextInitialized(ServletContextEvent sce) {
ServletContext ctx = sce.getServletContext();
ctx.setAttribute("onlineCount", new AtomicInteger(0));
}
-
AtomicInteger比synchronized块更高效,避免锁竞争 - 别存
int或Integer,它们不可变,每次++都是新对象,setAttribute不会自动更新上下文里的值 - 不要在监听器里启动定时任务或开线程去“刷新”这个值——它只该被 session 生命周期驱动
sessionCreated 和 sessionDestroyed 里怎么正确增减计数
关键不是“加1减1”,而是确保每次 session 创建/销毁都真实反映用户连接状态。常见错误是:监听器没注册、session 超时未触发销毁、浏览器关掉但没显式登出导致计数虚高。
实操要点:
立即学习“Java免费学习笔记(深入)”;
- 在
sessionCreated中调用((AtomicInteger) ctx.getAttribute("onlineCount")).incrementAndGet() - 在
sessionDestroyed中调用decrementAndGet(),**必须判空**,防止应用重启后 context 里没初始化就进销毁逻辑 - 确保
web.xml或注解正确注册监听器:@WebListener或<listener><listener-class>xxx</listener-class></listener> - 注意:session 默认 30 分钟超时,这段时间内用户关闭浏览器,服务端仍会计为“在线”,这是 HTTP 协议限制,无法靠监听器解决
前端怎么安全读取当前在线人数而不暴露 ServletContext
不能让前端直接访问 ServletContext,也不能把计数器塞进静态资源里。必须走一个受控的 HTTP 接口。
推荐做法是写一个简单 servlet 或 controller(比如 Spring MVC 的 @RestController):
@GetMapping("/api/online")
public int getOnlineCount(HttpServletResponse response) {
AtomicInteger count = (AtomicInteger) request.getServletContext()
.getAttribute("onlineCount");
return count != null ? count.get() : 0;
}
- 返回纯数字或 JSON,别带 HTML 模板——避免 XSS 风险
- 如果用了 Spring Boot,注意
ServletContext在非 Web 环境(如单元测试)里是 null,要加空判断 - 别用
request.getSession().getServletContext()多次获取,直接从 request 对象拿更稳妥
为什么有时候统计数比实际多,甚至负数
负数基本只有一种可能:decrementAndGet() 被调了比 incrementAndGet() 更多次。多出来的正数则多数源于 session 生命周期误判。
- 典型场景:用户开多个标签页,每个标签页触发一次 session 创建(尤其用了
isNew()判断不当的登录逻辑) - 开发环境热部署(如 Tomcat reload)会导致
contextDestroyed触发,但旧 session 还没清完,新 context 又初始化计数器为 0,老 session 销毁时去减一个不存在的计数器,就可能空指针或错减 - 集群部署下,
ServletContext是单机的,这个方案天然不支持分布式——别试图用 Redis 替换AtomicInteger后还叫它“ServletContextListener 方案” - 如果看到负数,立刻检查
sessionDestroyed是否在count为 0 时还执行了decrementAndGet()
真正的难点从来不在“怎么加减”,而在于你是否清楚每个 session 事件背后的容器行为、网络断连表现和部署拓扑约束。漏掉任意一环,数字就只是个幻觉。










