java NIO 与 netty 示例Demo

894 阅读7分钟

hello小伙伴们大家好,我是新人吃饱了,今天带大家领略一下NIO的世界。

  • 代码只是展示基本功能;
  • 个人水平有限,如有疑问请及时指正;

1、NIO操作file

//标准IO下的文件读取与写入
public static void main3(String[] args) throws Exception {
    RandomAccessFile file = new RandomAccessFile("C:\\Users\\Administrator\\Desktop\\121212\\111", "rw");
    FileChannel fileChannel = file.getChannel();
    ByteBuffer readBuffer = ByteBuffer.allocateDirect(5);
    int read = fileChannel.read(readBuffer);
    System.out.println("read:" + read);
    System.out.println("readBuffer:" + readBuffer.toString());
    //
    ByteBuffer writeBuffer = ByteBuffer.allocateDirect(5);
    writeBuffer.put(new byte[]{'!', '@', '#', '$', '%'});
    writeBuffer.flip();
    int write = fileChannel.write(writeBuffer, 0);
    System.out.println("write:" + write);
    System.out.println("success");
}

//内存映射,映射到的是堆外内存又叫做直接内存
public static void main2(String[] args) throws Exception {
    RandomAccessFile file = new RandomAccessFile("C:\\Users\\Administrator\\Desktop\\121212\\111", "rw");
    FileChannel fileChannel = file.getChannel();
    MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileChannel.size());
    //虽然映射磁盘文件减少了一次数据复制
    System.out.println(buffer instanceof DirectBuffer);  //true
    ByteBuffer put = buffer.put(new byte[]{'a', 'b', 'c', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'});
    buffer.get(new byte[12]);
    System.out.println(buffer == put);//true
    //buffer.force();
    System.out.println("success");
    //
}

//零拷贝
public static void main1(String[] args) throws Exception {
    RandomAccessFile file1 = new RandomAccessFile("C:\\Users\\Administrator\\Desktop\\121212\\111", "rw");
    FileChannel fileChannel1 = file1.getChannel();
    //
    RandomAccessFile file2 = new RandomAccessFile("C:\\Users\\Administrator\\Desktop\\121212\\1111", "rw");
    FileChannel fileChannel2 = file2.getChannel();
    //
    long l = fileChannel1.transferTo(1, 10, fileChannel2);
    System.out.println(l);
    System.out.println("success");
    //
}

2、NIO实现阻塞的客户端与服务端

//阻塞--客户端
public static void main4(String[] args) throws Exception {
    SocketChannel socketChannel1 = SocketChannel.open();
    socketChannel1.connect(new InetSocketAddress("127.0.0.1", 60001));
    //
    ByteBuffer writeBuffer = ByteBuffer.allocateDirect(5);
    writeBuffer.put(new byte[]{'!', '@', '#', '$', '%'});
    writeBuffer.flip();
    int write = socketChannel1.write(writeBuffer);
    System.out.println("write:" + write);
    //
    ByteBuffer readBuffer = ByteBuffer.allocate(5);
    socketChannel1.read(readBuffer);
    readBuffer.flip();
    System.out.println("read:" + Arrays.toString(readBuffer.array()));
    //
    System.out.println("success");
}

//阻塞--服务端
public static void main5(String[] args) throws Exception {
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 60000));
    SocketChannel channel;
    while ((channel = serverSocketChannel.accept()) != null) {
        System.out.println("channel:" + channel);
        ByteBuffer readBuffer = ByteBuffer.allocate(5);
        channel.read(readBuffer);
        readBuffer.flip();
        System.out.println("read:" + Arrays.toString(readBuffer.array()));
    }
}

3、NIO实现Reactor模型(服务端)

应该注意对2方面的抽象:线程模型的抽象,实体的抽象;表格如下:

模型分类优缺点
单reactor单个线程,单个selector,完成accept+read+write+业务逻辑处理,大量连接的情况下,CPU性能被浪费,容易成为系统瓶颈,redis使用的单reactor模型
单reactor+多线程再上面的基础上,reactor所在线程负责accept+read+write,业务逻辑处理交由单独的线程池来完成,可以使用JDK的线程池
主从reactor分为2个线程池,都是Reactor线程,acceptor负责accept,reactor负责read+write+业务处理,netty一般使用这种模型,由于全部是NIO线程,所以要求不能阻塞
主从reactor+多线程同第二种情况,一个acceptor,一个reactor线程池,再加上业务线程池,业务线程池可以阻塞,undertow使用的是该模式,一个默认的acceptor线程,ioThreads线程池负责read+write,workerThreads线程池负责阻塞的业务逻辑处理

3.1、单Reactor

package com.example.demo.test;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Set;

public class NioReactor2 {

    public static void main(String[] args) throws Exception {
        Selector selector = Selector.open();
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8989));
        ssc.configureBlocking(false);
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("ServerSocketChannel启动成功:" + ssc.hashCode());
        while (true) {
            selector.select(1000);
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            for (SelectionKey selectionKey : selectionKeys) {
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel sc = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel channel = sc.accept();
                    channel.configureBlocking(false);
                    channel.register(selector, SelectionKey.OP_READ);
                    System.out.println("acceptable:" + channel.hashCode());
                }
                if (selectionKey.isReadable()) {
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(10);
                    channel.read(buffer);
                    buffer.flip();
                    //需要使用attach方法传递数据
                    String message = new String(buffer.array());
                    String result = businessLogic(message);
                    System.out.println("read:" + message + ",经过处理后,需要返回:" + result);
                    channel.register(selector, SelectionKey.OP_WRITE, result);
                }
                if (selectionKey.isWritable()) {
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    Object result = selectionKey.attachment();
                    ByteBuffer buffer = ByteBuffer.allocate(20);
                    buffer.put(result.toString().getBytes(StandardCharsets.UTF_8));
                    buffer.flip();
                    channel.write(buffer);
                    System.out.println("write:" + Arrays.toString(buffer.array()));
                    channel.close();
                }
            }
            selectionKeys.clear();
        }
    }
    public static String businessLogic(String requestBody) {
        return "QWER" + requestBody;
    }
}

3.2、单Reactor+多线程

读取数据和写数据如何联动起来,返回读取的数据

package com.example.demo.test;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NioReactor2 {
    //1
    private static final ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws Exception {
        Selector selector = Selector.open();
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8989));
        ssc.configureBlocking(false);
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("ServerSocketChannel启动成功:" + ssc.hashCode());
        while (true) {
            selector.select(1000);
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            for (SelectionKey selectionKey : selectionKeys) {
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel sc = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel channel = sc.accept();
                    channel.configureBlocking(false);
                    channel.register(selector, SelectionKey.OP_READ);
                    System.out.println("acceptable:" + channel.hashCode());
                }
                if (selectionKey.isReadable()) {
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(10);
                    channel.read(buffer);
                    buffer.flip();
                    String message = new String(buffer.array());
                    //2
                    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> asyncBusinessLogic(message));
                    completableFuture.thenAccept(result -> {
                        System.out.println("read:" + message + ",经过处理后,需要返回:" + result);
                        try {
                            channel.register(selector, SelectionKey.OP_WRITE, result);
                        } catch (ClosedChannelException e) {
                            e.printStackTrace();
                        }
                    });
                    System.out.println("readable方法NIO线程执行完毕.....业务逻辑进入异步处理......");
                }
                if (selectionKey.isWritable()) {
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    Object result = selectionKey.attachment();
                    ByteBuffer buffer = ByteBuffer.allocate(20);
                    buffer.put(result.toString().getBytes(StandardCharsets.UTF_8));
                    buffer.flip();
                    channel.write(buffer);
                    System.out.println("write:" + Arrays.toString(buffer.array()));
                    channel.close();
                }
            }
            selectionKeys.clear();
        }
    }
    //3
    public static String asyncBusinessLogic(String requestBody) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "QWER" + requestBody;
    }
}

3.3、主从Reactor

package com.example.demo.test;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NioReactor1 {

    static class Reactor {
        private Selector selector;
        private ExecutorService executorService;

        public Reactor() {
            try {
                this.selector = Selector.open();
                executorService = Executors.newSingleThreadExecutor();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public void registerChannel(SocketChannel channel) {
            try {
                channel.register(this.selector, SelectionKey.OP_READ);
            } catch (ClosedChannelException e) {
                e.printStackTrace();
            }
        }

        public void run() {
            executorService.execute(() -> {
                try {
                    while (true) {
                        selector.select(1000);
                        Set<SelectionKey> selectionKeys = selector.selectedKeys();
                        for (SelectionKey selectionKey : selectionKeys) {
                            if (selectionKey.isReadable()) {
                                SocketChannel channel = (SocketChannel) selectionKey.channel();
                                ByteBuffer buffer = ByteBuffer.allocate(10);
                                channel.read(buffer);
                                buffer.flip();
                                System.out.println("read:" + new String(buffer.array()));
                                channel.register(this.selector, SelectionKey.OP_WRITE);
                            }
                            if (selectionKey.isWritable()) {
                                SocketChannel channel = (SocketChannel) selectionKey.channel();
                                ByteBuffer buffer = ByteBuffer.allocate(10);
                                buffer.put("abc".getBytes(StandardCharsets.UTF_8));
                                buffer.flip();
                                channel.write(buffer);
                                System.out.println("write:" + Arrays.toString(buffer.array()));
                                channel.close();
                            }
                        }
                        selectionKeys.clear();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }

    static class Acceptor {
        private Selector selector;
        private ExecutorService executorService;
        private Reactor reactor;

        public Acceptor() {
            try {
                this.selector = Selector.open();
                executorService = Executors.newSingleThreadExecutor();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public void addSubReactor(Reactor reactor) {
            this.reactor = reactor;
            reactor.run();
        }

        public void run() {
            if (this.reactor == null) {
                System.err.println("未设置subReactor");
                return;
            }
            executorService.execute(() -> {
                try {
                    ServerSocketChannel ssc = ServerSocketChannel.open();
                    ssc.bind(new InetSocketAddress(8989));
                    ssc.configureBlocking(false);
                    ssc.register(this.selector, SelectionKey.OP_ACCEPT);
                    System.out.println("ServerSocketChannel启动成功:" + ssc.hashCode());
                    while (true) {
                        selector.select(1000);
                        Set<SelectionKey> selectionKeys = selector.selectedKeys();
                        for (SelectionKey selectionKey : selectionKeys) {
                            if (selectionKey.isAcceptable()) {
                                ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
                                SocketChannel channel = serverSocketChannel.accept();
                                channel.configureBlocking(false);
                                reactor.registerChannel(channel);
                                System.out.println("acceptable:" + channel.hashCode());
                            }
                        }
                        selectionKeys.clear();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }

    public static void main(String[] args) {
        Reactor reactor = new Reactor();
        Acceptor acceptor = new Acceptor();
        acceptor.addSubReactor(reactor);
        acceptor.run();
    }
}

3.4、主从Reactor+多线程

参考2.2的实现方案

4、NIO实现客户端(阻塞&非阻塞)

当前线程以阻塞或者非阻塞的方式获取到结果,主要的方式:

  • 当前线程发起一个连接请求,注册到Reactor线程池中,Reactor线程池负责connect,write,read,获取到数据后放入到公共的变量中
  • 当前线程和Reactor线程通过公共变量ReadWriteContext进行交互,阻塞或非阻塞方式
  • 关键难点在于线程的转换和交互,将请求加入到Reactor线程池后reactor.register(socketChannel, readWriteContext);当前线程如何获取执行结果;
package com.flux.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class Reactor3 {

    private static final Logger logger = LoggerFactory.getLogger(Reactor3.class);

    private static final List<Reactor> reactorList = new ArrayList<>();

    static class ReadWriteContext {
        private volatile byte[] writeData;
        private volatile byte[] readData;

        public ReadWriteContext(byte[] writeData) {
            this.writeData = writeData;
        }

        public byte[] getWriteData() {
            return writeData;
        }

        public void setWriteData(byte[] writeData) {
            this.writeData = writeData;
        }

        public byte[] getReadData() {
            return readData;
        }

        public void setReadData(byte[] readData) {
            this.readData = readData;
        }
    }

    static class HttpUtil {
        public static String sendAndBlockingGet(int port, byte[] data) {
            try {
                //1、获取channel并将请求发送到Reactor线程池中,可以保存channel并从缓存中获取(连接池)
                SocketChannel socketChannel = SocketChannel.open();
                socketChannel.configureBlocking(false);
                socketChannel.connect(new InetSocketAddress(port));
                ReadWriteContext readWriteContext = new ReadWriteContext(data);
                //reactor负载均衡,这里简单化,只取第一个
                reactorList.get(0).register(socketChannel, readWriteContext);
                //2、线程交互(当前线程和reactor所在线程),判断是否有值,这里使用了循环阻塞的方式,浪费CPU资源
                while (true) {
                    if (readWriteContext.getReadData() != null) {
                        return new String(readWriteContext.getReadData());
                    }
                    Thread.sleep(200);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    //将selector绑定到线程,并开始运行,并且提供对外的register接口
    //selector+thread,整体看做一个reactor实例
    static class Reactor {

        private Selector selector;

        public Reactor(int index) {
            try {
                this.selector = Selector.open();
                Thread thread = new Thread(this::run);
                thread.setName("Reactor-" + index);
                thread.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
            reactorList.add(this);
        }

        public void register(SocketChannel socketChannel, ReadWriteContext rwCtx) {
            try {
                socketChannel.register(this.selector, SelectionKey.OP_CONNECT, rwCtx);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public void run() {
            try {
                while (true) {
                    selector.select(2000);
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    logger.info("selectionKeys-size:{}", selectionKeys.size());
                    for (SelectionKey selectionKey : selectionKeys) {
                        if (selectionKey.isConnectable()) {
                            SocketChannel channel = (SocketChannel) selectionKey.channel();
                            channel.configureBlocking(false);
                            Selector selector = selectionKey.selector();
                            //3、判断连接是否完成
                            if (channel.finishConnect()) {
                                ReadWriteContext rwCtx = (ReadWriteContext) selectionKey.attachment();
                                logger.info("connect success:{}", channel.hashCode());
                                channel.register(selector, SelectionKey.OP_WRITE, rwCtx);
                            }
                        }
                        if (selectionKey.isWritable()) {
                            SocketChannel channel = (SocketChannel) selectionKey.channel();
                            Selector selector = selectionKey.selector();
                            ReadWriteContext rwCtx = (ReadWriteContext) selectionKey.attachment();
                            ByteBuffer byteBuffer = ByteBuffer.allocate(10);
                            byteBuffer.put(rwCtx.getWriteData());
                            byteBuffer.flip();
                            channel.write(byteBuffer);
                            logger.info("write success:{}", new String(rwCtx.getWriteData()));
                            channel.register(selector, SelectionKey.OP_READ, rwCtx);
                        }
                        if (selectionKey.isReadable()) {
                            SocketChannel channel = (SocketChannel) selectionKey.channel();
                            ReadWriteContext rwCtx = (ReadWriteContext) selectionKey.attachment();
                            ByteBuffer byteBuffer = ByteBuffer.allocate(10);
                            channel.read(byteBuffer);
                            byteBuffer.flip();
                            rwCtx.setReadData(byteBuffer.array());
                            logger.info("read success:{}", new String(byteBuffer.array()));
                            channel.close();
                        }
                    }
                    selectionKeys.clear();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //NIO-clint,阻塞和非阻塞的拿到数据
    public static void main(String[] args) throws Exception {
        //初始化多个reactor,这部分肯定是框架来处理的,休眠是为了通过日志判断reactor线程是否已经启动
        new Reactor(0);
        new Reactor(1);
        Thread.sleep(10000);
        //一般在业务场景中,我们通过如下方式来使用httpUtil发送请求并获取数据
        String result = HttpUtil.sendAndBlockingGet(9898, "abc".getBytes());
        System.out.println("result:" + result);
    }
}

5、Netty实现SimpleHttpServer

  • Acceptor接收到socket连接--->Reactor线程池read--->业务线程池---->Reactor线程池write
  • 模拟undertow的线程模型
  • 相比与NIO,增加了解码的配置,减少了线程模型的思考,体现了netty作为一个优秀的非阻塞IO框架的优势,极大简化了我们的开发
  • 详细的netty示例可以查看官方文档:netty.io/,基于TCP的编解码器都有涉及;
  • netty封装的NIO,本身是基于TCP&UDP的,http,ftp等均基于之上做的文本等协议,作为inboundHandler或者outboundHandler提供,我们只需要提供基础的businessHander即可;
package com.example.netty.demo.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

public class NettyServer {


    static class Util {
        public static void print(Object msg) {
            System.out.println("[" + Thread.currentThread().getName() + "]:" + msg);
        }
    }


    //自定义线程工厂,主要是为不同的线程修改名称
    static class MyThreadFactory implements ThreadFactory {
        AtomicInteger ai = new AtomicInteger(0);
        String prefix = "";

        public MyThreadFactory(String prefix) {
            this.prefix = prefix;
        }

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setName(prefix + ai.getAndIncrement());
            return thread;
        }
    }

    //Handles a server-side channel.
    static class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) throws Exception {
            String uri = fullHttpRequest.uri();
            ByteBuf content = fullHttpRequest.content();
            Util.print("received,uri:" + uri + ";content:" + content.toString(CharsetUtil.UTF_8));
            // 创建http响应
            FullHttpResponse response = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK,
                    Unpooled.copiedBuffer("{\"result\":\"success\"}", CharsetUtil.UTF_8));
            // 设置头信息,write到客户端
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8");
            channelHandlerContext.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }
    }


    public static void main(String[] args) throws Exception {
        //1、反应器线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup(new MyThreadFactory("boss"));
        EventLoopGroup workerGroup = new NioEventLoopGroup(new MyThreadFactory("worker"));
        final EventExecutorGroup businessGroup = new DefaultEventExecutorGroup(16, new MyThreadFactory("business"));

        try {
            //2、helper
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    //3、设置NIO的通道类型
                    .channel(NioServerSocketChannel.class)
                    .handler(new ChannelInitializer<ServerSocketChannel>() {
                        @Override
                        protected void initChannel(ServerSocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                    Util.print("channelActive");
                                    super.channelActive(ctx);
                                }
                            });
                        }
                    })
                    //4、装配子通道的Pipeline流水线,在父通道成功接收一个连接>创建一个子通道channel>调用ChannelInitializer实例的initChannel()
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            //解码器,或者使用HttpRequestDecoder & HttpResponseEncoder
                            ch.pipeline().addLast(new HttpServerCodec());
                            //对于post请求
                            ch.pipeline().addLast(new HttpObjectAggregator(65535));
                            //业务处理
                            ch.pipeline().addLast(businessGroup, new HttpRequestHandler());
                        }
                    })
                    //5、通道参数
                    .option(ChannelOption.SO_BACKLOG, 128)
                    //6、子通道参数
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            //7、Bind and start to accept incoming connections.可以添加listener或者使用同步方式sync
            ChannelFuture f = b.bind(8080).sync();
            Util.print("EchoServer.runServer:端口绑定成功,服务端启动成功:8080");
            //8、Wait until the server socket is closed.In this example, this does not happen, but you can do that to gracefully shut down your server.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

最终的线程模型为:

C:\Users\qingwa>jstack 16324
2022-03-31 23:28:40
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.181-b13 mixed mode):

"business0" #15 prio=5 os_prio=0 tid=0x000000001fb1e800 nid=0x3940 waiting on condition [0x000000002095e000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076d57f4b8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at io.netty.util.concurrent.SingleThreadEventExecutor.takeTask(SingleThreadEventExecutor.java:243)
        at io.netty.util.concurrent.DefaultEventExecutor.run(DefaultEventExecutor.java:64)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at java.lang.Thread.run(Thread.java:748)

"worker0" #14 prio=5 os_prio=0 tid=0x000000001f490800 nid=0x3a64 runnable [0x000000002085e000]
   java.lang.Thread.State: RUNNABLE
        at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method)
        at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll(WindowsSelectorImpl.java:296)
        at sun.nio.ch.WindowsSelectorImpl$SubSelector.access$400(WindowsSelectorImpl.java:278)
        at sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:159)
        at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
        - locked <0x000000076d514338> (a io.netty.channel.nio.SelectedSelectionKeySet)
        - locked <0x000000076d510898> (a java.util.Collections$UnmodifiableSet)
        - locked <0x000000076d510748> (a sun.nio.ch.WindowsSelectorImpl)
        at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
        at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:101)
        at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:68)
        at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:813)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:460)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at java.lang.Thread.run(Thread.java:748)

"boss0" #13 prio=5 os_prio=0 tid=0x000000001edca000 nid=0x2730 runnable [0x000000002075e000]
   java.lang.Thread.State: RUNNABLE
        at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method)
        at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll(WindowsSelectorImpl.java:296)
        at sun.nio.ch.WindowsSelectorImpl$SubSelector.access$400(WindowsSelectorImpl.java:278)
        at sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:159)
        at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
        - locked <0x000000076d483a68> (a io.netty.channel.nio.SelectedSelectionKeySet)
        - locked <0x000000076d040850> (a java.util.Collections$UnmodifiableSet)
        - locked <0x000000076d03cb00> (a sun.nio.ch.WindowsSelectorImpl)
        at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
        at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:101)
        at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:68)
        at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:813)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:460)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at java.lang.Thread.run(Thread.java:748)

"Service Thread" #12 daemon prio=9 os_prio=0 tid=0x000000001e7fc800 nid=0xd40 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

6、Netty实现SimpleHttpClient

参考官网例子即可