java基础-网络编程(Socket)技术选型入门之NIO技术

时间:2023-03-08 16:26:14

             java基础-网络编程(Socket)技术选型入门之NIO技术

                                         作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

一.传统的网络编程

1>.编写socket通信的MyServer,使用分线程完成和每个client的通信。

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/
package cn.org.yinzhengjie.socket; import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket; public class MyServer {
public static void main(String[] args) throws Exception {
//服务器套接字
ServerSocket ss = new ServerSocket(8888) ;
while(true){
//接受连接,
System.out.println("正在接受连接.....");
Socket sock = ss.accept();
new CommThread(sock).start();
}
}
} /**
* 服务器和每个客户端的通信线程
*/
class CommThread extends Thread{
private Socket sock; public CommThread(Socket sock){
this.sock = sock ;
} public void run() {
try {
//获取远程地址和端口
InetSocketAddress addr = (InetSocketAddress) sock.getRemoteSocketAddress();
int port = addr.getPort();
String ip = addr.getAddress().getHostAddress();
System.out.printf("有人连接进来了!! : %s , %d\r\n", ip, port); //向客户端发送消息
int index = 0;
OutputStream out = sock.getOutputStream();
while (true) {
index ++ ;
out.write(("yinzhengjie" + index + "\r\n").getBytes());
out.flush();
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

MyServer.java 文件内容

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/
package cn.org.yinzhengjie.socket; import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket; public class MyClient {
public static void main(String[] args) throws Exception {
Socket s = new Socket("localhost" ,8888) ;
System.out.println("连接到服务器了!!");
InputStream in = s.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in)) ;
String line = null ;
//获取服务端发来的消息
while((line = br.readLine())!= null){
System.out.println("收到消息 : " + line);
}
}
}

MyClient.java 文件内容

  我们先开启上面的服务端,再开启2个客户端,测试结果如下:

正在接受连接.....
正在接受连接.....
有人连接进来了!! : 127.0.0.1 , 52704
正在接受连接.....
有人连接进来了!! : 127.0.0.1 , 52710

server端输出的输入戳这里

 连接到服务器了!!
收到消息 : yinzhengjie1
收到消息 : yinzhengjie2
收到消息 : yinzhengjie3
收到消息 : yinzhengjie4
收到消息 : yinzhengjie5
收到消息 : yinzhengjie6
收到消息 : yinzhengjie7
收到消息 : yinzhengjie8
收到消息 : yinzhengjie9
收到消息 : yinzhengjie10
收到消息 : yinzhengjie11
收到消息 : yinzhengjie12
收到消息 : yinzhengjie13
收到消息 : yinzhengjie14
收到消息 : yinzhengjie15
收到消息 : yinzhengjie16
收到消息 : yinzhengjie17
收到消息 : yinzhengjie18
收到消息 : yinzhengjie19
收到消息 : yinzhengjie20
收到消息 : yinzhengjie21
收到消息 : yinzhengjie22
收到消息 : yinzhengjie23
收到消息 : yinzhengjie24
收到消息 : yinzhengjie25
收到消息 : yinzhengjie26
收到消息 : yinzhengjie27
收到消息 : yinzhengjie28
收到消息 : yinzhengjie29
收到消息 : yinzhengjie30
收到消息 : yinzhengjie31
收到消息 : yinzhengjie32
收到消息 : yinzhengjie33
收到消息 : yinzhengjie34
收到消息 : yinzhengjie35
收到消息 : yinzhengjie36

client1输出的数据戳这里

连接到服务器了!!
收到消息 : yinzhengjie1
收到消息 : yinzhengjie2
收到消息 : yinzhengjie3
收到消息 : yinzhengjie4
收到消息 : yinzhengjie5
收到消息 : yinzhengjie6
收到消息 : yinzhengjie7

client2输出的数据戳这里

2>.分析传统的套接字存在的缺陷(以上述代码为例)

java基础-网络编程(Socket)技术选型入门之NIO技术

  综上图所述,服务端存在一个缺陷,当客户端的连接数量过多,main线程就会开启过多的线程去响应,如此长久下去,JVM内存耗尽是迟早的事情,因此上述代码再测试使用连接较少的情况下是可取的,但是在连接过多的情况则不推荐使用,实际上我们能想到这一点,开发JAVA语言的大佬们怎么可能想不到这一点呢?因此这些大佬们早在JDK1.4版本就给出了相应的解决方案,它是本篇博客的重点,即新型IO(简称NIO)。

  

二.NIO简介

1>.I/O瓶颈出现

  java.io流类在使用时需要复制大量的小数据块;在不切换线程上下问的情况下没有办法在多个源之间进行数据的复用;也无法利用操作系统的高性能IO特性,比如内存的file的映射(虚拟内存)。

2>.什么是NIO

  在 JDK 1. 4 中 新 加入 了 NIO( New Input/ Output) 类, 引入了一种基于通道和缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了在 Java 堆和 Native 堆中来回复制数据。

  NIO 是一种同步非阻塞的 IO 模型。同步是指线程不断轮询 IO 事件是否就绪,非阻塞是指线程在等待 IO 的时候,可以同时做其他任务。同步的核心就是 Selector,Selector 代替了线程本身轮询 IO 事件,避免了阻塞同时减少了不必要的线程消耗;非阻塞的核心就是通道和缓冲区,当 IO 事件就绪时,可以通过写道缓冲区,保证 IO 的成功,而无需线程阻塞式地等待。

3>.NIO的组成部分

  Java NIO (New IO)是JDK1.4后引入的新输入/输出API,提供基于缓冲区(buffer)的块写入/读取,而以前的I/O是基于流(Stream)的方式,NIO基于块的IO操作,将最耗时的缓存区读取和填充交由底层操作系统实现,因此速度上要快得多。NIO主要由以下三个核心部分组成:

  第一部分:Buffer 

    答:为什么说NIO是基于缓冲区的IO方式呢?因为,当一个链接建立完成后,IO的数据未必会马上到达,为了当数据到达时能够正确完成IO操作,在BIO(阻塞IO)中,等待IO的线程必须被阻塞,以全天候地执行IO操作。为了解决这种IO方式低效的问题,引入了缓冲区的概念,当数据到达时,可以预先被写入缓冲区,再由缓冲区交给线程,因此线程无需阻塞地等待!

关于Buffer的相关概念如下:

1>.capacity
  答:容量,即数组的长度。
2>.limit
  答:限制,可以使用的空间大小。写入完成后,各种操作都是在position和limit之间操作数据。 3>.position
  答:位置,指针的位置,开始操纵地方,它会随着读写操作自动更新。Position类似于File的指针。 4>.mark
  答:记号,可以通过reset方法将指针重置到mark的位置上。 5>.flip
  答:拍板操作 ,设置 limit为当前位置值并将position设置为0,以备用于读取。即limit = position , post = 0 ; 他们按照大小的排列顺序如下:0 <= mark <= position <= limit <= capacity
 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/
package cn.org.yinzhengjie.nio; import java.nio.ByteBuffer; public class TestNIO {
public static void main(String[] args) {
mark();
} public static void mark(){
//定义一个字节数组
byte[] bytes = {'y','i','n','z','h','e','n','g','j','i','e'};
//将字节数组包装成缓冲区
ByteBuffer buf = ByteBuffer.wrap(bytes);
//修改缓冲区的指针位置为2
buf.position(2);
//对当前指针做标记,即标记当前指针为2
buf.mark();
//在做完标记后,修改指针位置为8
buf.position(8);
//重置指针,这样它会回到上一个mar标记的位置!如果没有配置mark,默认回到0这个指针位置
buf.reset();
//打印当前指针位置
System.out.println("当前指针位置是:"+buf.position());
}
} /*
以上代码执行结果如下:
当前指针位置是:2
*/

测试mark

  第二部分:Channel

    答:JAVA中的NIO是通过channel的API进行操控的,通道是 I/O 传输发生时通过的入口,而缓冲区是这些数 据传输的来源或目标。对于离开缓冲区的传输,您想传递出去的数据被置于一个缓冲区,被传送到通道。对于传回缓冲区的传输,一个通道将数据放置在您所提供的缓冲区中。换句话说,channel是到硬件设备、文件、socket或网络组件的打开的连接,这些组件都可以执行IO操作。通道可以是open或closed状态,创建时打开,close后一直保持closed状态,可以通过isOpen判断通道状态。

  第三部分:Selector

    答:通道和缓冲区的机制,使得线程无需阻塞地等待IO事件的就绪,但是总是要有人来监管这些IO事件。这个工作就交给了selector来完成,这就是所谓的同步。Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪,这就是所说的轮询。一旦这个方法返回,线程就可以处理这些事件。

 4>.NIO的变化

  第一:BUffer类允许在JVM和OS之间使用很小的内存复制技术来移动数据,不需要过度开销,比如像切换字节的顺序;

  第二:统一的Channel类族允许在Buffer,文件或Socket之间直接交换数据而不需要想原有的Stream类那样借助于中间媒介;

  第三:NIO首次提供了文件锁定机制;

三.Java NIO和IO的主要区别

  java NIO和传统IO还是有区别的,大致分为以下三点:

1>.面向流与面向缓冲

  Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。

  Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。

  Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

2>.阻塞与非阻塞IO

  Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。

  Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

3>.选择器(Selectors

  Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。关于选择器画了一个草图如下:

java基础-网络编程(Socket)技术选型入门之NIO技术

四.NIO网络编程

1>.NIO实现文件复制

  使用NIO技术实现文件复制,核心就是FileChannel + ByteBuffer完成文件的读写。

/*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/
package cn.org.yinzhengjie.nio; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; public class FileDemo {
public static void main(String[] args) throws Exception {
//文件输出
FileInputStream fis = new FileInputStream("D:\\BigData\\JavaSE\\yinzhengjieData\\1.txt");
//源文件通道,由于我们的fis对象是文件输入流,因此获取到的srcFC通道也只能进行读取操作!
FileChannel srcFC = fis.getChannel();
//输出流
FileOutputStream fos = new FileOutputStream("D:\\BigData\\JavaSE\\yinzhengjieData\\2.txt") ;
//输出文件通道,由于我们的fos对象是文件输出流,因此获取到的destFC通道也只能进行写入操作!
FileChannel destFC = fos.getChannel();
//分配NIO的4个字节缓冲区,在堆区分配内存。可通过jvisualvm工具查看。
ByteBuffer buf = ByteBuffer.allocate(4) ;
//拷贝数据
while(srcFC.read(buf) != -1){
//进行拍板操作,即limit为当前position的位置,position回到起始位置
buf.flip();
//进行上述操作后,就可以把读取到的数据写入到文件中
destFC.write(buf) ;
//重新启用整个缓冲区,即position回到起始位置,limit为capacity的容量!注意:如果这里不clear的话,文件又没有读取完毕,就会陷入死循环状态!
buf.clear();
}
}
}

2>.优化“编写socket通信的MyServer,使用分线程完成和每个client的通信。

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/
package cn.org.yinzhengjie.nio.socket; import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set; public class MyNioServer {
public static void main(String[] args) throws Exception {
//开启ServerSocket通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//指定服务端地址
SocketAddress addr = new InetSocketAddress("www.yinzhengjie.org.cn", 8888);
//绑定地址
ssc.bind(addr);
//配置非阻塞模式
ssc.configureBlocking(false) ;
//打开挑选器,我们可以通过“selector.keys()”获取注册集,我们还可以通过“selector.selectedKeys()”获取挑选集。没有取消集合,如果想要取消某个key,可以调用它的取消方法(key.cancel();)。
Selector selector = Selector.open();
//注册通道并绑定事件,由于我们是服务端,只需要绑定ACCEPT事件即可!这样我们就可以直接才能够ssc对象里面取得我们感兴趣的事件啦!
ssc.register(selector, SelectionKey.OP_ACCEPT) ; System.out.println("服务端已经开启,等待接收连接中......");
while(true){
//开始挑选
selector.select();
//取得挑选出来的key
Set<SelectionKey> keys = selector.selectedKeys();
//得到迭代器,这是selector挑选出来的key,这些key代表着不同的事件!
Iterator<SelectionKey> it = keys.iterator();
//判断迭代器是否含有下一个key,然后在里面判断每一个key属于哪个类型?比如可读?可写?可连接?
while(it.hasNext()){
//从迭代器中随机取出一个key!
SelectionKey key = it.next() ;
//判断是否发生接受事件
if(key.isAcceptable()){
//处理接受过程,“key.channel()”里面存放的就是服务端感兴趣的keys(返回的是SelectableChannel类型),而我们只绑定ACCEPT时间,因此里面存放的应该只有accept()事件哟!因此我们可以直接从ssc中取得相应的accept()事件也是可行的!
SocketChannel sc = ssc.accept();
//得到远程的地址的端口
InetSocketAddress remoteAddr = (InetSocketAddress)sc.getRemoteAddress();
String ip = remoteAddr.getAddress().getHostAddress();
int port = remoteAddr.getPort() ;
//配置非阻塞,如果想要设置为阻塞模式,我们将里面的值设置为true即可!不推荐!因为非阻塞模式可以轻松实现并发操作!
sc.configureBlocking(false) ;
//注册sc,因为我们的sc是新产生的对象,它也需要向挑选器中注册自己感兴趣的事件!我们这里对读,写,连接事件都感兴趣,他们直接用“|”运算进行连接。
sc.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT) ;
System.out.printf("%s : %d 连接进来了\r\n" , ip , port);
}
//读取发来的消息
if(key.isReadable()){
//拿到可读key的通道
SocketChannel sc00 = (SocketChannel) key.channel();
//调用我们定义好的方法读取数据
String str = readStrFromChannel(sc00) ;
System.out.println(sc00 + " 说: " + str);
}
//可写
if(key.isWritable()){
SocketChannel sc00 = (SocketChannel) key.channel();
writeStrToChannel(sc00 , "hi ,I'm Yinzhengjie !!!");
Thread.sleep(1000);
}
//完成连接
if(key.isConnectable()){
SocketChannel sc00 = (SocketChannel) key.channel();
sc00.finishConnect() ;
}
//删除当前key,如果不删除,下次挑选出来还会再次处理!注意,“key.cancel();”是取消当前绑定的key事件,一旦执行,你们在注册集合中注册的key也将不复存在!
it.remove();
}
}
} /**
* 从通道中读取文本
*/
public static String readStrFromChannel(SocketChannel sc){
//定义一个内存字节输出流对象,用于存取读取到的数据。
ByteArrayOutputStream baos = new ByteArrayOutputStream() ;
try {
//分配NIO的1024个字节缓冲区,在堆区分配内存。可通过jvisualvm工具查看。
ByteBuffer buf = ByteBuffer.allocate(1024) ;
int len = -1 ;
//读完数据的判定条件是当通道中无数据时,通道的长度为0。
while((len = sc.read(buf)) != 0){
//将读取到的数据写入到内存流中,buf.array()是拿到后台每次读取的缓冲区的数组。
baos.write(buf.array(), 0 ,len) ;
//读完之后将清空,注意,这里的情况不是清空数组的数据哟!而是将position的值改为0,而limit的位置不变!
buf.clear() ;
}
return new String(baos.toByteArray()) ;
} catch (Exception e) {
e.printStackTrace();
}
return "" ;
} /**
* 写入消息到通道
*/
public static void writeStrToChannel(SocketChannel sc , String str){
try {
//通过数组包装缓冲区。换句话说,包装数组成为缓冲区。
ByteBuffer buf = ByteBuffer.wrap(str.getBytes());
//将缓冲区数据写入到sc对象中
sc.write(buf) ;
} catch (Exception e) {
e.printStackTrace();
}
}
}

MyNioServer.java 文件内容

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/
package cn.org.yinzhengjie.nio.socket; import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Set; public class MyNioClient {
public static void main(String[] args) throws Exception {
//指定服务端地址
SocketAddress serverAddr = new InetSocketAddress("www.yinzhengjie.org.cn" , 8888) ;
//和服务端建立连接
SocketChannel sc = SocketChannel.open(serverAddr) ;
//配置非阻塞模式
sc.configureBlocking(false) ;
//打开挑选器
Selector sel = Selector.open() ;
//注册通道,得到注册key,由于客户端我们只需要绑定读和写的操作就好!
SelectionKey key = sc.register(sel , SelectionKey.OP_READ | SelectionKey.OP_WRITE) ; int index = 0 ;
while(true){
//开始挑选,返回值n表示已经挑选出来客户单看兴趣的事件啦!
int n = sel.select() ;
//如果n大于0,说明挑选器已经挑选到key事件了,接下来我们就需要完成挑选的类型的判断
if(n > 0){
//获取挑选器挑选出来key的集合
Set<SelectionKey> keys = sel.selectedKeys();
//判断这个key是否可读,如果可读我们就将数据读取出来!
if(key.isReadable()){
String str = MyNioServer.readStrFromChannel(sc) ;
System.out.println("收到消息 : " + str);
}
//判断这个key是否可写,如果可写我们就往这个key中写入数据。为了试验效果更佳,我们让每次写入时睡眠一秒钟!
if(key.isWritable()){
MyNioServer.writeStrToChannel(sc , "hello" + (index ++));
Thread.sleep(1000);
}
//注意,处理完key的事件后,记得将keys进行清空(删除当前已经处理的keys)操作!否则数据接收可能会收到影响!
keys.clear();
}
}
}
}

MyNioClient.java 文件内容

  我们先开启上面的服务端,再开启客户端,测试结果如下:

服务端已经开启,等待接收连接中......
127.0.0.1 : 64763 连接进来了
java.nio.channels.SocketChannel[connected local=/127.0.0.1:8888 remote=/127.0.0.1:64763] 说: hello0
java.nio.channels.SocketChannel[connected local=/127.0.0.1:8888 remote=/127.0.0.1:64763] 说: hello1
java.nio.channels.SocketChannel[connected local=/127.0.0.1:8888 remote=/127.0.0.1:64763] 说: hello2
java.nio.channels.SocketChannel[connected local=/127.0.0.1:8888 remote=/127.0.0.1:64763] 说: hello3
java.nio.channels.SocketChannel[connected local=/127.0.0.1:8888 remote=/127.0.0.1:64763] 说: hello4
java.nio.channels.SocketChannel[connected local=/127.0.0.1:8888 remote=/127.0.0.1:64763] 说: hello5
java.nio.channels.SocketChannel[connected local=/127.0.0.1:8888 remote=/127.0.0.1:64763] 说: hello6
java.nio.channels.SocketChannel[connected local=/127.0.0.1:8888 remote=/127.0.0.1:64763] 说: hello7
java.nio.channels.SocketChannel[connected local=/127.0.0.1:8888 remote=/127.0.0.1:64763] 说: hello8
java.nio.channels.SocketChannel[connected local=/127.0.0.1:8888 remote=/127.0.0.1:64763] 说: hello9
java.nio.channels.SocketChannel[connected local=/127.0.0.1:8888 remote=/127.0.0.1:64763] 说: hello10
java.nio.channels.SocketChannel[connected local=/127.0.0.1:8888 remote=/127.0.0.1:64763] 说: hello11
java.nio.channels.SocketChannel[connected local=/127.0.0.1:8888 remote=/127.0.0.1:64763] 说: hello12

服务端接收数据戳这里

收到消息 : hi ,I'm Yinzhengjie !!!
收到消息 : hi ,I'm Yinzhengjie !!!
收到消息 : hi ,I'm Yinzhengjie !!!
收到消息 : hi ,I'm Yinzhengjie !!!
收到消息 : hi ,I'm Yinzhengjie !!!
收到消息 : hi ,I'm Yinzhengjie !!!
收到消息 : hi ,I'm Yinzhengjie !!!
收到消息 : hi ,I'm Yinzhengjie !!!
收到消息 : hi ,I'm Yinzhengjie !!!
收到消息 : hi ,I'm Yinzhengjie !!!
收到消息 : hi ,I'm Yinzhengjie !!!
收到消息 : hi ,I'm Yinzhengjie !!!
收到消息 : hi ,I'm Yinzhengjie !!!
收到消息 : hi ,I'm Yinzhengjie !!!
收到消息 : hi ,I'm Yinzhengjie !!!

客户端接收数据戳这里

3>.单个线程的NIO存在的缺陷

  答:我们知道传统的NIO解决并发的问题是需要不断的开启线程实现并发操作,这样导致一个不好的结果无法接受大量并发的连接,因为当连接数过大时,会开启过多的新线程去响应每一个新连接的线程。当开启的新线程数量过多时,服务端需要进行多个线程的上线文切换,从而降低了执行效率,与此同时,还会占用过多的资源消耗!而我们通过NIO技术优化后,只需要开启一个线程就能相应多个客户端的连接,但是这也产生了一个新的问题,由于NIO在处理并发连接时,是通用轮询选择器的方式去相应每一个客户端,当相应某个客户端连接执行的时间过长,这回导致这个执行其它线程时间过长。换句话说,每次处理客户端连接在服务端都是在一个线程里按序执行,如果某个任务它比较占时间那么就会拖慢整个服务器的执行效率!

  因此为了解决不断创建多个线程或者只启用一个线程的存在的问题,就出现了一种池化技术的解决方案!