前言

之前一篇文章简单介绍了NIO,并附了一个简单的例子,但是自己试一下就会知道,简单的使用NIO是无法满足开发需要的,因为NIO处理的思路和日常servlet加spring中习惯的一连接一线程有很大不同。

NIO与多线程

上篇那个例子实现了一个简单的NIO,但是实际使用中我们不可能仅仅在单线程下使用,肯定会使用多线程提高处理的效率,但是这样就会有几个难点。

Readable和Writeable的空触发

之前的文章的代码有写到通过key去判断readable和writeable这两个事件,从而去读写,但是会出现以下情况

实际上这2个事件并不像看上去那样,在writeable之中负责写内容,以最常用的http的请求-返回场景来看,通常上返回的处理是在read处理完后写内容到channel里去,writeable里只做一个flush操作,并不是readable只负责读,writeable只负责写。

在多线程下,这两个事件的处理尤为糟糕,并不能简单的去实现Runable再套用函数进去执行,因为这两个事件本身在key被remove前,会一直触发,如果第一个执行的线程执行完之前,这个key因为没有remove被再次获取到去执行,那么可能就会产生很多问题,比如读取不到数据(已经被第一个线程读取了),channel已经关闭(第一个线程或其他线程写完后,本机或者远端关闭了连接)。这样都会影响多线程实际执行。

Readable和Writeable事件产生一般对应着待读数据的到来和待写数据的就绪,就绪后产生key提醒应用去处理。但是有2种情况下会产生实际没什么事可做的空触发。一种是有线程在处理key,但是没有处理完,key未被取消,这时每次select依旧会出现这个key,但实际此key正在处理中。另外一种就是只要连接还在,NIO会频繁触发Writeable事件,这就是为什么1中写道通常writeable只做flush,同样,Readable只要有数据待读就一直会触发,这会使做消息聚合、报文拼接的时候很难处理。

请求与返回的处理

NIO因为是非阻塞的原因,请求与返回并不是一一对应的,仅做服务端还好,以接收请求为主,在Readable事件处理完后写返回内容就可以了。但是如果既做请求端又做服务端的情况下,请求Write后,处理Read的早已不知道是哪个时间点的哪个进程了,此时如果请求和返回有关联,则较难一一对应上,这一点连著名的Netty框架都没有提供一些简便方式去完成,不过这也体现了他的纯粹,在dubbo中是通过CountDownLatch实现的,一般还有使用futureTask去实现异步转同步的操作。

事件的处理机制

通常情况下,接收到请求肯定要根据请求的内容进行不同的处理,简单来说起码要根据交易码不同分类不同的交易处理。这样的话面对复杂协议(如http),从报文处理到请求分类都有一个很复杂的逻辑和过程,如果没有合适的事件处理机制模型,会导致处理起来有太多的判断代码,难以维护。

NIO多线程使用的一个例子

这里参照了Java大神Doug Lea的思路,提供一个多线程的例子,主要思路包括

  1. 通过注册时的attach,将该channel的处理类直接关联起来,后面取出key时直接调用处理类去处理key
  2. 处理类在处理时添加一个volatile成员做状态标记,该channel正在处理事件时,该标记为处理中,此时该channel的其他key处理到时都将判断该标记,如果该channel在处理中,则直接退出等待下次select出来继续判断。
  3. 使用了线程池去处理
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
Selector selector = Selector.open();
serverSocketChannel.bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
//主ServerSocket绑定Accept事件,处理类为Accept
SelectionKey key=serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT, new Accept(serverSocketChannel, selector));
for(;;) {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
//分发处理key
dispatchTask(selectionKey);
}
selectionKeys.clear();
}
} private static void dispatchTask(SelectionKey selectionKey) {
//这里的attachment,针对ServerSocketChannel,是Accept,针对进来的连接SocketChannel,是TaskHandler
Runnable runnable = (Runnable)selectionKey.attachment();
if (runnable != null) {
runnable.run();
}
}
public class Accept implements Runnable{
ServerSocketChannel serverSocketChannel;
Selector selector;
public Accept(ServerSocketChannel serverSocketChannel, Selector selector) {
this.serverSocketChannel=serverSocketChannel;
this.selector=selector;
} @Override
public void run() {
try {
SocketChannel socketChannel=serverSocketChannel.accept();
//给进来的连接注册selector,这里直接通过构造函数来了
new TaskHandler(socketChannel,selector);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class TaskHandler implements Runnable{
private SocketChannel socketChannel;
private SelectionKey selectionKey;
private volatile int state = PROCESSED;
private static final ExecutorService pool = Executors.newFixedThreadPool(4); static final int PROCESSING=1;
static final int PROCESSED=2; public TaskHandler(SocketChannel socketChannel, Selector selector) throws IOException {
this.socketChannel=socketChannel;
socketChannel.configureBlocking(false);
//把自己attach上去
this.selectionKey=socketChannel.register(selector,SelectionKey.OP_READ,this);
selector.wakeup();
} @Override
public void run() {
//判断处理状态
if(state==PROCESSED)
{
pool.execute(new Process(selectionKey));
}
}
class Process implements Runnable{
private SelectionKey selectionKey; public Process(SelectionKey selectionKey) {
this.selectionKey = selectionKey;
state=PROCESSING;
} @Override
public void run() {
try {
if(socketChannel.socket().isClosed())
return ;
if(selectionKey.isReadable())
{
read();
}
} catch (IOException e) {
e.printStackTrace();
}
} private void read() throws IOException {
ByteBuffer byteBuffer=ByteBuffer.allocate(64);
SocketChannel socketChannel= (SocketChannel) selectionKey.channel();
int read;
System.out.println("readding");
if ((read = socketChannel.read(byteBuffer)) < 0) {
state = PROCESSED;
socketChannel.close();
return;
}
System.out.println(new String(byteBuffer.array()));
state=PROCESSED;
}
}
}

Java中的NIO进阶的更多相关文章

  1. Java中的NIO基础知识

    上一篇介绍了五种NIO模型,本篇将介绍Java中的NIO类库,为学习netty做好铺垫 Java NIO 由3个核心组成,分别是Channels,Buffers,Selectors.本文主要介绍着三个 ...

  2. JAVA中的NIO (New IO)

    简介 标准的IO是基于字节流和字符流进行操作的,而JAVA中的NIO是基于Channel和Buffer进行操作的. 传统IO graph TB; 字节流 --> InputStream; 字节流 ...

  3. java中的NIO和IO到底是什么区别?20个问题告诉你答案

    摘要:NIO即New IO,这个库是在JDK1.4中才引入的.NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多. 本文分享自华为云社区<jav ...

  4. Java中的NIO和IO的对比分析

    总的来说,java中的IO和NIO主要有三点区别: IO NIO 面向流 面向缓冲 阻塞IO 非阻塞IO 无 选择器(Selectors) 1.面向流与面向缓冲 Java NIO和IO之间第一个最大的 ...

  5. Java中的NIO及IO

    1.概述 Java NIO(New IO) 是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API.NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同, ...

  6. JAVA中的NIO(二)

    一.内存文件映射 内存文件映射允许我们创建和修改那些因为太大而不能放入内存中的文件.有了内存文件映射,我们就可以假定整个文件都在内存中,而且可以完全把文件当作数组来访问. package com.dy ...

  7. JAVA中的NIO(一)

    1.IO与NIO IO就是普通的IO,或者说原生的IO.特点:阻塞式.内部无缓冲,面向流. NIO就是NEW IO,比原生的IO要高效.特点:非阻塞.内部有缓存,面向缓冲. 要实现高效的IO操作,尤其 ...

  8. JAVA 中BIO,NIO,AIO的理解

    [转自]http://qindongliang.iteye.com/blog/2018539 ?????????????????????在高性能的IO体系设计中,有几个名词概念常常会使我们感到迷惑不解 ...

  9. JAVA 中BIO,NIO,AIO的理解以及 同步 异步 阻塞 非阻塞

    在高性能的IO体系设计中,有几个名词概念常常会使我们感到迷惑不解.具体如下: 序号 问题 1 什么是同步? 2 什么是异步? 3 什么是阻塞? 4 什么是非阻塞? 5 什么是同步阻塞? 6 什么是同步 ...

随机推荐

  1. AWT10-位图

    1.概述 Graphics提供了drwaImage(Image image)来绘制位图,该方法需要一个位图做参数. 步骤: 1.创建Image的子类对象BufferedImage(int width, ...

  2. WindowsPhone8中LongListSelector的扩展解决其不能绑定SelectdeItem的问题

    微软在Wp8中集成了LongListSelector, 但是该控件在ViewModel中不能实现的SelectdeItem双向绑定,因为其不是DependencyProperty没办法只能实现扩展! ...

  3. spring boot 项目使用idea正常打包后执行总是出现异常,解决办法

    众所周知,spring boot 在构建项目时,确实为Java程序员们省了不少力气,打包项目非常容易. 上周在项目中遇到打包后的项目总是无法运行,困扰了我们团队将近两天才找到原因. 图和真相在这里.

  4. 移动端 Swiper

    一.什么是swiper 开源.免费.强大的触摸滑动插件 Swiper常用于移动端网站的内容触摸滑动 Swiper能实现触屏焦点图.触屏Tab切换.触屏多图切换等常用效果 #二.如何使用 1.首先加载插 ...

  5. 移动端SCSS

    一.什么是SASS SASS是一种强化CSS的辅助工具,提供了许多便利的写法,大大节省了设计者的时间,使得CSS的开发,变得简单可维护. #二.安装和使用(VS Code中) #1.安装 下载扩展文件 ...

  6. Flink集群监控

    prometheus+grafana 监控hadoop.yarn https://blog.csdn.net/c275090933/article/details/82108014 Prometheu ...

  7. MybatisPlus_01

    目录 1.1 简介 1.1.1 特性 1.1.2 框架结构 2.1 快速开始 2.1.1 2.1.2 yaml文件配置 2.1.3 编码 2.1.4 测试 3.1 思考 1.1 简介 MyBatis- ...

  8. SQL精华总结索引类型优化SQL优化事务大表优化思维导图❤️

    索引类型 从数据结构角度: B+树索引, hash索引,基于哈希表实现,只有全值匹配才有效.以链表的形式解决冲突.查找速度非常快 O(1) 全文索引,查找的是文本中的关键词,而不是直接比较索引中的值, ...

  9. python菜鸟教程学习:数据结构

    列表方法 list.append(x):把一个元素添加到列表的结尾,相当于 a[len(a):] = [x]. list.extend(L):通过添加指定列表的所有元素来扩充列表,相当于 a[len( ...

  10. 【Python 1-8】Python手把手教程之——管理列表List

    遍历列表 在日常开发中,我们经常需要遍历列表的所有元素,对每个元素执行相同的操作.例如,在管理商场的蔬菜时候,需要给所有的蔬菜商品都打7折,并重新生成价格.当我们需要对列表中的每个元素都执行相同的操作 ...