如何实现Spring WebSocket全局同步计数器,多客户端共享计时状态
全局同步WebSocket计时器实现方案
要实现多客户端同步显示同一个计时器状态,核心是把计时状态抽离为全局共享变量,用独立线程驱动计时逻辑,并统一管理所有在线客户端会话,具体实现如下:
核心思路
- 用静态变量存储全局计时状态(时、分、秒)和运行标记,保证所有客户端共享同一状态
- 用线程安全的集合维护所有在线WebSocket会话,避免并发操作问题
- 启动独立后台线程处理计时逻辑,避免每个客户端请求重复创建线程
- 统一向所有在线客户端推送计时消息,确保同步
完整代码实现
package primer; import java.io.IOException; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import org.springframework.stereotype.Component; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; @Component public class SocketTextHandler extends TextWebSocketHandler { // 全局共享的计时变量,volatile保证多线程可见性 private static volatile int hours = 0; private static volatile int minutes = 0; private static volatile int seconds = 0; // 标记计时器是否正在运行 private static volatile boolean timerRunning = false; // 线程安全的会话集合,存储所有在线客户端 private static final Set<WebSocketSession> sessions = new CopyOnWriteArraySet<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 客户端连接成功后,加入会话集合 sessions.add(session); // 新客户端连接后,立即发送当前计时状态 sendCurrentTimeToSession(session); } @Override public void afterConnectionClosed(WebSocketSession session, org.springframework.web.socket.CloseStatus status) throws Exception { // 客户端断开连接后,从会话集合移除 sessions.remove(session); } @Override public void handleTextMessage(WebSocketSession session, TextMessage message) throws InterruptedException, IOException { String clientMessage = message.getPayload(); if (clientMessage.startsWith("start")) { // 只有当计时器未运行时,才启动计时线程 synchronized (SocketTextHandler.class) { if (!timerRunning) { timerRunning = true; startTimerThread(); } } } else if (clientMessage.startsWith("stop")) { // 停止计时器并重置状态 synchronized (SocketTextHandler.class) { timerRunning = false; hours = 0; minutes = 0; seconds = 0; // 向所有客户端发送重置后的状态 broadcastTime(); } } } // 启动计时线程,每秒更新时间并推送 private void startTimerThread() { new Thread(() -> { while (timerRunning) { try { Thread.sleep(1000); // 更新计时状态 synchronized (SocketTextHandler.class) { seconds++; if (seconds >= 60) { seconds = 0; minutes++; if (minutes >= 60) { minutes = 0; hours++; if (hours >= 60) { hours = 0; } } } } // 向所有客户端广播当前时间 broadcastTime(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }).start(); } // 向所有在线客户端推送当前时间 private void broadcastTime() { String timeStr = String.format("%02d:%02d:%02d", hours, minutes, seconds); TextMessage timeMessage = new TextMessage(timeStr); // 遍历所有会话发送消息,跳过已关闭的会话 for (WebSocketSession session : sessions) { if (session.isOpen()) { try { session.sendMessage(timeMessage); } catch (IOException e) { e.printStackTrace(); } } } } // 向单个会话发送当前时间 private void sendCurrentTimeToSession(WebSocketSession session) throws IOException { String timeStr = String.format("%02d:%02d:%02d", hours, minutes, seconds); session.sendMessage(new TextMessage(timeStr)); } }
关键细节说明
- volatile关键字:修饰计时变量和运行标记,确保多线程下的状态可见性,避免线程缓存旧值
- CopyOnWriteArraySet:线程安全的会话集合,适合频繁遍历、较少修改的场景,避免并发修改异常
- 同步块:在修改全局状态或启动/停止计时器时加锁,避免多线程竞争导致的状态混乱
- 独立计时线程:只在第一次收到start指令时启动,后续客户端发送start不会重复创建线程,保证全局唯一计时逻辑
- 会话管理:在连接建立/断开时维护会话集合,新客户端连接立即同步当前计时状态,确保一致性
内容的提问来源于stack exchange,提问作者Artemiy
相关产品推荐
相关产品推荐

