部署于Tomcat的jPOS客户端多线程并发请求抛出Socket类异常咨询
多线程下jPOS客户端连接异常原因分析
实际运行表现
- 自研jPOS客户端部署于Tomcat容器,对接长期稳定运行的jPOS服务端(其他客户端可正常连接),通过REST接口触发8583报文发送流程
- 单线程串行执行请求时功能完全正常,多线程提压后出现两类典型异常:
java.net.SocketException: Socket is closed,堆栈栈顶指向jPOSBaseChannel的重连逻辑java.io.EOFException,堆栈栈顶指向CSChannel读取报文长度的逻辑
异常触发的核心原因
单例非线程安全的CSChannel实例被多线程并发共享访问,具体代码缺陷点如下:
- 通道初始化无并发保护:
JposClient类的成员变量csChannel是所有请求线程共享的实例,getCSChannel()方法中的非空判断没有加并发锁,高并发下会出现多线程同时初始化通道的情况 - 通道内部状态被并发篡改:jPOS的
BaseChannel本身不具备线程安全性,内部维护的Socket连接、输入输出流、连接状态标识会被多个线程同时读写:- 某一线程操作完成后关闭Socket,其他线程复用该已关闭连接时,就会抛出
Socket is closed异常 - 多个线程同时往同一个Socket写8583报文时,会导致报文边界错位,服务端无法正常解析报文后主动断开连接,客户端读取报文时就会抛出
EOFException
- 某一线程操作完成后关闭Socket,其他线程复用该已关闭连接时,就会抛出
- 冗余的connect调用放大问题:代码在
send方法开头每次执行都会调用csChannel.connect(),当通道已处于连接状态时,该调用会触发jPOS内置的重连逻辑,多线程并发触发重连时,会频繁出现旧Socket已关闭、新连接未建立的中间异常态 - 协议通道本身的设计限制:
CSChannel是基于TCP长连接的8583通道,设计上支持keep-alive长连接,但同一时间仅能串行收发报文,原生不支持多线程并发共享同一个通道实例
常见修复思路
- 每个请求线程独立创建、使用、销毁
CSChannel实例,不要将通道作为类的成员变量全局共享 - 如需复用长连接降低握手开销,可实现通道池化管理,同一时间将单个通道实例仅分配给一个线程使用,请求完成后将通道放回连接池
- 移除
send方法中冗余的connect调用,仅在检测到通道断开时才触发重连流程
内容的提问来源于stack exchange,提问作者rookie
相关产品推荐
相关产品推荐

