前言

之前一篇文章简单介绍了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. Elastic Search 学习之路(三)—— tutorial demo

    一.ElasticSearch tutorial demo example 1. 单机.local.CRUD操作 实现方式: SpringBoot + ElasticSearch 拷贝的小demo,原 ...

  2. vue 实现一个商城项目

    在学习了 vue 之后,决定做一个小练习,仿写了一个有关购物商城的小项目.下面就对项目做一个简单的介绍. 项目源码: github 项目的目录结构 -assets 与项目有关的静态资源,包括 css, ...

  3. kali linux没有ip解决办法

    故障情况 今天打开kali202001复测环境,发现自启动ssh竟然连不上. 上到kali主机使用命令:ifconfig 查看发现没有ethh0网卡显示,看来是kal获取不到ip地址导致的 继续查看发 ...

  4. 程序猿的浪漫:用python画动态爱心

    初级画心 学Python,感觉你们的都好复杂,那我来个简单的,我是直接把心形看作是一个正方形+两个半圆:

  5. Linux环境下使用logrotate工具实现nginx日志切割

    一. 前提背景及需求 nginx运行日志默认保存在nginx安装目录下的 /usr/local/nginx/logs 文件夹, 包含access.log和error.log两个文件. (1) acce ...

  6. 对路径binroslyn..的访问被拒绝

    一开始的解决办法就是把bin下的文件都删除了,但是roslyn文件夹下的部分文件一直被占用,必须进程中把vbcscompiler进程干掉,才能删除,再重新编译,就没问题了.

  7. Python常用内置对象

    1.在python中处理的一切都是对象. 2.内置对象可直接使用,如数字.字符串.列表.del等. 3.非内置对象需要导入模块才能使用,如正弦函数sin(x),随机数产生函数random()等.

  8. .NET Core AWS S3云存储

    前言 最近有需要用到AWS S3云存储上传附件,这里对利用.NET或.NET Core在调用SDK APi需要注意的一点小问题做个记录,或许能对后续有用到的童鞋提供一点帮助 AWS S3云存储 官方已 ...

  9. 4.自定义view-进度条控件

    1.效果 2.实现原理 画圆,画圆弧,画文字 外部控制进度,通过invalidate()方法更新 核心代码: @Override protected void onDraw(Canvas canvas ...

  10. 5. Longest Palindromic Substring最大回文子串

    int sta = 0; int max = 1; public String longestPalindrome(String s) { /* 判断回文有两种: 1.最大回文子序列求长度: 用动态规 ...