Hutool 文档潜在问题--规避潜在的线程溢出可能

issue url

版本情况

hutool版本: 5

问题描述(包括截图)

参考文档构建代码:AIO封装-AioServer和AioClient
其中 AioClient 的示例如下:

public void foobar() {
AioClient client = new AioClient(new InetSocketAddress("localhost", 8899), new SimpleIoAction() {

@Override
public void doAction(AioSession session, ByteBuffer data) {
if(data.hasRemaining()) {
Console.log(StrUtil.utf8Str(data));
session.read();
}
Console.log("OK");
}
});

client.write(ByteBuffer.wrap("Hello".getBytes()));
client.read();

client.close();
}

若直接在生产环境调用此代码,将必然出现线程溢出 OOM

原因

AioClient
该类有三个构造方法:

  • public AioClient(InetSocketAddress address, IoAction<ByteBuffer> ioAction)
  • public AioClient(InetSocketAddress address, IoAction<ByteBuffer> ioAction, SocketConfig config)
  • public AioClient(AsynchronousSocketChannel channel, IoAction<ByteBuffer> ioAction, SocketConfig config)

前两个方法都会默认调用 private static AsynchronousSocketChannel createChannel(InetSocketAddress address, int poolSize) 隐含构建线程池
但在 AioClientclose方法中,线程池没有被关闭。反复通过上述示例代码进行调用后,线程池可能会膨胀到非常夸张的地步,直到内存不足产生OOM。

正确示例

正确的方式是只构建一个 AsynchronousChannelGroup,并反复使用。在不需要时,进行关闭。

private static final AsynchronousChannelGroup GROUP = AsynchronousChannelGroup.withFixedThreadPool(//
Runtime.getRuntime().availableProcessors(), // 默认线程池大小
ThreadFactoryBuilder.create().setNamePrefix("Huool-socket-").build()//
);

public static void closeAioGroup() throws IOException {
GROUP.shutdown();
}

private static AsynchronousSocketChannel createChannel(InetSocketAddress address) {

AsynchronousSocketChannel channel;
try {
channel = AsynchronousSocketChannel.open(group);
} catch (IOException e) {
throw new IORuntimeException(e);
}

try {
channel.connect(address).get();
} catch (InterruptedException | ExecutionException e) {
IoUtil.close(channel);
throw new SocketRuntimeException(e);
}
return channel;
}

public void foobar() {
AioClient client = new AioClient(createChannel(new InetSocketAddress("localhost", 8899)), new SimpleIoAction() {

@Override
public void doAction(AioSession session, ByteBuffer data) {
if(data.hasRemaining()) {
Console.log(StrUtil.utf8Str(data));
session.read();
}
Console.log("OK");
}
});

client.write(ByteBuffer.wrap("Hello".getBytes()));
client.read();

client.close();
}

建议

  1. 建议更新文档,防止因按文档使用导致 OOM
  2. 建议标记另两个构造方法为过时的,且打印日志提醒不能在生产环境使用