• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

java网络编程-nio学习阻塞和非阻塞

武飞扬头像
沐雪架构师
帮助3

一、阻塞

  • 阻塞模式下,相关方法都会导致线程暂停

    • ServerSocketChannel.accept 会在没有连接建立时让线程暂停

    • SocketChannel.read 会在没有数据可读时让线程暂停

    • 阻塞的表现其实就是线程暂停了,暂停期间不会占用 cpu,但线程相当于闲置

  • 单线程下,阻塞方法之间相互影响,几乎不能正常工作,需要多线程支持

  • 但多线程下,有新的问题,体现在以下方面

    • 32 位 jvm 一个线程 320k,64 位 jvm 一个线程 1024k,如果连接数过多,必然导致 OOM,并且线程太多,反而会因为频繁上下文切换导致性能降低

    • 可以采用线程池技术来减少线程数和线程上下文切换,但治标不治本,如果有很多连接建立,但长时间 inactive,会阻塞线程池中所有线程,因此不适合长连接,只适合短连接。

服务端事例代码:

  1.  
    // 使用 nio 来理解阻塞模式, 单线程
  2.  
    // 0. ByteBuffer
  3.  
    ByteBuffer buffer = ByteBuffer.allocate(16);
  4.  
    // 1. 创建了服务器
  5.  
    ServerSocketChannel ssc = ServerSocketChannel.open();
  6.  
     
  7.  
    // 2. 绑定监听端口
  8.  
    ssc.bind(new InetSocketAddress(8080));
  9.  
     
  10.  
    // 3. 连接集合
  11.  
    List<SocketChannel> channels = new ArrayList<>();
  12.  
    while (true) {
  13.  
    // 4. accept 建立与客户端连接, SocketChannel 用来与客户端之间通信
  14.  
    log.debug("connecting...");
  15.  
    SocketChannel sc = ssc.accept(); // 阻塞方法,线程停止运行
  16.  
    log.debug("connected... {}", sc);
  17.  
    channels.add(sc);
  18.  
    for (SocketChannel channel : channels) {
  19.  
    // 5. 接收客户端发送的数据
  20.  
    log.debug("before read... {}", channel);
  21.  
    channel.read(buffer); // 阻塞方法,线程停止运行
  22.  
    buffer.flip();
  23.  
    debugRead(buffer);
  24.  
    buffer.clear();
  25.  
    log.debug("after read...{}", channel);
  26.  
    }
  27.  
    }
学新通

客户端:

  1.  
    SocketChannel sc = SocketChannel.open();
  2.  
    sc.connect(new InetSocketAddress("localhost", 8080));
  3.  
    System.out.println("waiting...");

二、非阻塞

  • 非阻塞模式下,相关方法都会不会让线程暂停

    • 在 ServerSocketChannel.accept 在没有连接建立时,会返回 null,继续运行

    • SocketChannel.read 在没有数据可读时,会返回 0,但线程不必阻塞,可以去执行其它 SocketChannel 的 read 或是去执行 ServerSocketChannel.accept

    • 写数据时,线程只是等待数据写入 Channel 即可,无需等 Channel 通过网络把数据发送出去

  • 但非阻塞模式下,即使没有连接建立,和可读数据,线程仍然在不断运行,白白浪费了 cpu

  • 数据复制过程中,线程实际还是阻塞的(AIO 改进的地方)

服务器端demo代码,客户端代码不变

  1.  
    // 使用 nio 来理解非阻塞模式, 单线程
  2.  
    // 0. ByteBuffer
  3.  
    ByteBuffer buffer = ByteBuffer.allocate(16);
  4.  
    // 1. 创建了服务器
  5.  
    ServerSocketChannel ssc = ServerSocketChannel.open();
  6.  
    ssc.configureBlocking(false); // 非阻塞模式
  7.  
    // 2. 绑定监听端口
  8.  
    ssc.bind(new InetSocketAddress(8080));
  9.  
    // 3. 连接集合
  10.  
    List<SocketChannel> channels = new ArrayList<>();
  11.  
    while (true) {
  12.  
    // 4. accept 建立与客户端连接, SocketChannel 用来与客户端之间通信
  13.  
    SocketChannel sc = ssc.accept(); // 非阻塞,线程还会继续运行,如果没有连接建立,但sc是null
  14.  
    if (sc != null) {
  15.  
    log.debug("connected... {}", sc);
  16.  
    sc.configureBlocking(false); // 非阻塞模式
  17.  
    channels.add(sc);
  18.  
    }
  19.  
    for (SocketChannel channel : channels) {
  20.  
    // 5. 接收客户端发送的数据
  21.  
    int read = channel.read(buffer);// 非阻塞,线程仍然会继续运行,如果没有读到数据,read 返回 0
  22.  
    if (read > 0) {
  23.  
    buffer.flip();
  24.  
    debugRead(buffer);
  25.  
    buffer.clear();
  26.  
    log.debug("after read...{}", channel);
  27.  
    }
  28.  
    }
  29.  
    }
学新通

三、多路复用

单线程可以配合 Selector 完成对多个 Channel 可读写事件的监控,这称之为多路复用。

  • 多路复用仅针对网络 IO、普通文件 IO 没法利用多路复用

  • 如果不用 Selector 的非阻塞模式,线程大部分时间都在做无用功,而 Selector 能够保证

    • 有可连接事件时才去连接

    • 有可读事件才去读取

    • 有可写事件才去写入

      • 限于网络传输能力,Channel 未必时时可写,一旦 Channel 可写,会触发 Selector 的可写事件

学新通

好处

  • 一个线程配合 selector 就可以监控多个 channel 的事件,事件发生线程才去处理。避免非阻塞模式下所做无用功

  • 让这个线程能够被充分利用

  • 节约了线程的数量

  • 减少了线程上下文切换

创建

Selector selector = Selector.open();

绑定 Channel 事件

也称之为注册事件,绑定的事件 selector 才会关心

  1.  
    channel.configureBlocking(false);
  2.  
    SelectionKey key = channel.register(selector, 绑定事件);
  • channel 必须工作在非阻塞模式

  • FileChannel 没有非阻塞模式,因此不能配合 selector 一起使用

  • 绑定的事件类型可以有

    • connect - 客户端连接成功时触发

    • accept - 服务器端成功接受连接时触发

    • read - 数据可读入时触发,有因为接收能力弱,数据暂不能读入的情况

    • write - 数据可写出时触发,有因为发送能力弱,数据暂不能写出的情况

监听 Channel 事件

可以通过下面三种方法来监听是否有事件发生,方法的返回值代表有多少 channel 发生了事件

方法1,阻塞直到绑定事件发生  (常用)

int count = selector.select();

方法2,阻塞直到绑定事件发生,或是超时(时间单位为 ms)

int count = selector.select(long timeout);

方法3,不会阻塞,也就是不管有没有事件,立刻返回,自己根据返回值检查是否有事件

int count = selector.selectNow();

select 何时不阻塞

  • 事件发生时

    • 客户端发起连接请求,会触发 accept 事件

    • 客户端发送数据过来,客户端正常、异常关闭时,都会触发 read 事件,另外如果发送的数据大于 buffer 缓冲区,会触发多次读取事件

    • channel 可写,会触发 write 事件

    • 在 linux 下 nio bug 发生时

  • 调用 selector.wakeup()

  • 调用 selector.close()

  • selector 所在线程 interrupt

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfhhffc
系列文章
更多 icon
同类精品
更多 icon
继续加载