I always thought that asynchronous execution is about efficient resource utilization and thread safety, but today I ran into Netty’s strange behavior.
public class Example { public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); try { bootstrap.group(group) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel channel) { channel.pipeline() .addLast(new ChannelOutboundHandlerAdapter() { @Override public void read(ChannelHandlerContext ctx) { String id = String.valueOf(Thread.currentThread().getId()); ctx.writeAndFlush(Unpooled.wrappedBuffer(id.getBytes(StandardCharsets.UTF_8))) .addListener(ChannelFutureListener.CLOSE); } }); } }) .bind("localhost", 1234) .sync() .channel() .closeFuture() .syncUninterruptibly(); } finally { group.shutdownGracefully() .syncUninterruptibly(); } } }
When I connected for the first time, I got 16. Then 17, 18, 19, etc. Every connection was executed on a new thread! Why? What’s the point of Netty, if it’s multi-threaded?
Advertisement
Answer
NioEventLoopGroup
uses worker threads to utilize multiple CPU cores. As per the no-argument constructor javadoc:
NioEventLoopGroup()
Create a new instance using the default number of threads, the default ThreadFactory and the SelectorProvider which is returned by SelectorProvider.provider().
The deafult thread count as per MultithreadEventLoopGroup
will be twice the number of available processors:
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt( "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
Generally this should be enough to saturate the CPU without creating a huge number of threads. Spawn too few threads and you won’t get full CPU utilization. Spawn too many and you will waste substantial time context switching between them.