【Spring】验证 @ServerEndpoint 的类成员变量线程安全

前言

最近有 websocket 的需求。探索 @ServerEndpoint 的类成员变量特点。
这里类比 @Controller 讨论 @ServerEndpoint 类成员变量是否线程安全。

猜想来源

网上的教程大多数都这么展示程序:

@Component
@ServerEndpoint(value = "/ws")
public class MyWsEndpoint {

    private final MyContext context = new MyContext();

    @OnOpen
    public void onOpen(Session session) {
        
    }
    // ... 省略
}    

但是,同样的写法,类比 @Controller ,必然会产生线程安全问题。

@Controller
public class MyController {
    private static final Logger logger = LoggerFactory.getLogger(MyController.class);

    private final MyContext context = new MyContext();

    @GetMapping("/normal")
    @ResponseBody
    public String normal() {
        
    }
}

需要一种快速验证的方式, @ServerEndpoint 的类成员变量则线程安全


验证方法

  • 最简单的用例,把读和写拆成两个步骤,如果读到了其他线程的值,则为不安全的情况
        // 每个请求独立自增
        int currentCount = ac.incrementAndGet();
        
        // 多线程写
        context.setCurrentCount(currentCount);
        // 多线程读
        if (context.getCurrentCount() != currentCount) {
            logger.error("线程不安全!!!");
        }
  • 浏览器触发请求,多线程 debug 打断点 制造并发场景
    在这里插入图片描述

@Controller 的情况

  • 线程不安全
    原因是 @Controller 的类默认是单例的
    在这里插入图片描述

@ServerEndpoint 的情况

贴一个前端测试代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ws client</title>
</head>
<body>

</body>

<script>
    let ws = new WebSocket("ws://localhost:8080/ws")
    ws.onopen = function () {
        ws.send("hello")
    }

    ws.onmessage = function (message) {
        console.log(message)
    }
</script>
</html>
  • 线程安全
    在这里插入图片描述

后记

  • 上文验证了 @ServerEndpoint 的类成员变量是线程安全的。
  • 但是存在矛盾:
    • 上面用例 @Component@ServerEndpoint 共同作用于一个类上
    • Spring 容器管理 @Component 默认是单例的

package com.sqx.controller; import javafx.beans.binding.Bindings; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @ServerEndpoint("/webSocket/{userId}") @Component @Slf4j public class WebSocketServer { /** * 记录当前在线连接数 */ private static int onlineCount = 0; /** * 存放每个客户端对应的MyWebSocket对象, 使用map对象便于根据userId来获取对应的WebSocket */ private static Map<String, Set<WebSocketServer>> webSocketMap = new HashMap<>(); /** * 与客户端的连接会话,需要通过它来给客户端发送数据 */ private Session session; /** * 客户端连接会话对应的用户Id */ private String userId; /** * 判断当前userId是否已创建连接 * @param userId * @return */ public boolean checkOnline(String userId){ for (String key : webSocketMap.keySet()) { if(userId.equals(key)){ return true; } } return false; } /** * 连接建立成功调用的方法 */ @OnOpen public void onOpen(Session session, @PathParam("userId") String userId) { this.session = session; this.userId = userId; //在线数加1 addOnlineCount(); //根据该用户当前是否已经在别的终端登录进行添加操作 if (webSocketMap.containsKey(this.userId)) { Set<WebSocketServer> set = webSocketMap.get(this.userId); set.add(this); webSocketMap.put(this.userId,set); }else{ Set<WebSocketServer> addUserSet = new HashSet<>(); addUserSet.add(this); webSocketMap.put(this.userId, addUserSet); } } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { if(webSocketMap.containsKey(userId)){ webSocketMap.remove(userId); //在线数减1 subOnlineCount(); } } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息 * @param session 客户端的连接会话 */ @OnMessage public void onMessage(String message, Session session) { try{ //todo 需要添加 JSONObject json = JSON.parseObject(message); }catch (Exception e){ e.printStackTrace(); } } /** * 发生错误时调用的方法 * * @param session 客户端的连接会话 * @param error 异常错误 */ @OnError public void onError(Session session, Throwable error) { } /** * @Title: sendMessageToUser * : 发送消息给用户下的所有终端 * @param @param userId 用户id * @param @param message 发送的消息 * @param @return 发送成功返回true,反则返回false */ public static Boolean sendMessageToUser(String userId,String message){ if (webSocketMap.containsKey(userId)) { for (WebSocketServer ws : webSocketMap.get(userId)) { try { ws.session.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); return false; } } return true; } return false; } private static synchronized int getOnlineCount() { return WebSocketServer.onlineCount; } private static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } private static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; } @Override public int hashCode() { return super.hashCode(); } @Override public boolean equals(Object obj) { return super.equals(obj); } }这个加测试
03-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值