【基础】
阻塞 | 非阻塞 |
---|---|
线程持续等待资源中数据准备完成,直到返回响应结果。 | 线程直接返回结果,不会持续等待资源准备数据结束后才响应结果。 |
异步 | 同步 |
---|---|
异步则指主动请求数据后便可以继续处理其它任务,随后等待IO操作完毕的通知 | 同步一般指主动请求并等待IO操作完成的方式。 |
【io与nio区别】
io | nio | 描述 |
---|---|---|
面向流Stream | 面向缓冲Buffer | io是面向流的,nio是面向缓冲区的。【面向流】每次从流中读取一个或者多个字节,直至读取完所有的字节,他们没有被缓存在任何地方;也不能前后移动流中的数据【面向缓冲区】数据读取到一个稍后处理的缓冲区,需要时可在缓冲区中前后移动。但是需要检查是否该缓冲区中包含所需要的处理的数据,确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。 |
阻塞IO | 非阻塞IO | 【io】各种流是阻塞的,当一个线程调用read()或者write()时,该线程被阻塞,直到数据被读取完毕或者写入完毕,该线程才会被释放。【nio】非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果通道中没有,就不会获取任何数据。直至数据变的可以读取之前,该线程可以继续做其他的事情,写入也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。一个单独的线程可以管理多个输入和输出通道 |
无 | 选择器Selectors | 【nio】选择器允许一个单独的线程来监视多个输入通道,也就是非阻塞模式,使用一个单独的线程来选择通道,通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程容易管理多个通道。Java NIO的selectors允许一条线程去监控多个channels的输入,你可以向一个selector上注册多个channel,然后调用selector的select()方法判断是否有新的连接进来或者已经在selector上注册时channel是否有数据进入。selector的机制让一个线程管理多个channel变得简单。 |
NIO优点:
1、通过通道channel注册到选择器selector上的状态,来实现一种客户端与服务端的通信。通道中的数据的读取是通过Buffer,一种非阻塞的读取方式。
2、Selector多路复用器,单线程模型,线程的资源开销小。
Channel(通道)
io操作对读写read/write方法调用,可能会因为没有数据可读而阻塞,直到数据响应。可能会无限制的阻塞,关键是调用这个方法的时候,无法预知该方法是否会被阻塞。
Buffer(缓冲区)
它是一个缓冲区,实际上是一个容器,一个连续数组。
Channel提供从文件、网络读取数据的渠道,但是读写的数据都必须经过Buffer。
【测试 小demo】
@GetMapping("/0408")
public String getThread(){
StringBuffer sb = new StringBuffer();
try {
InputStream is = new FileInputStream("D:\\Frozen\\testFile\\2020-4-8redant_test.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(is,"GBK"));
sb.append("当前运行的进程 : " + Thread.currentThread().getName());
sb.append("<br/>");
sb.append("读取文件的第一行内容为:" + reader.readLine());
sb.append("<br/>");
sb.append("读取文件的第二行内容为:" + reader.readLine());
sb.append("<br/>");
sb.append("读取文件的第三行内容为:" + reader.readLine());
sb.append("<br/>");
sb.append("读取文件的第四行内容为:" + reader.readLine());
sb.append("<br/>");
sb.append("读取文件的第五行内容为:" + reader.readLine());
sb.append("<br/>");
}catch (Exception e){
e.printStackTrace();
}
return sb.toString();
}
【nio:基于通道 & 缓冲数据的文件复制】
@GetMapping("/0409")
public void baseChannelData(){
try {
// 1. 获取数据源 和 目标传输地的输入输出流(此处以数据源 = 文件为例)
File file = new File("D:\\Frozen\\testFile\\20200409.txt");
FileInputStream fin = new FileInputStream(file);
File outfile = new File("D:\\Frozen\\testFile\\20200410.txt");
FileOutputStream fout = new FileOutputStream(outfile);
// 2. 获取数据源的输入输出通道
FileChannel fcin = fin.getChannel();
FileChannel fcout = fout.getChannel();
// 3. 创建 缓冲区 使用allocate()静态方法
ByteBuffer buff = ByteBuffer.allocate(1024);//字节
//再额外给你塞几个
StringBuffer sb = new StringBuffer();
sb.append("★★★★★★★★★★★★★★★★★★★\n\r\t");
sb.append("★★★★★★★★★★★★★★★★★★★/n/r/t");
sb.append("2020年4月9日15:14:19");
sb.append("2020年4月9日15:14:19");
sb.append("2020年4月9日15:14:19");
sb.append("2020年4月9日15:14:19");
sb.append("2020年4月9日15:14:19");
sb.append("2020年4月9日15:14:19");
ByteBuffer sendBuff = ByteBuffer.wrap(sb.toString().getBytes("GBK"));
buff.put(sendBuff);
// 4. 从通道读取数据 & 写入到缓冲区
// 注:若 以读取到该通道数据的末尾,则返回-1
fcin.read(buff);
// 5. 传出数据准备:将缓存区的写模式 转换->> 读模式
buff.flip();
// 6. 从 Buffer 中读取数据 & 传出数据到通道
fcout.write(buff);
// 7. 重置缓冲区
// 目的:重用现在的缓冲区,即 不必为了每次读写都创建新的缓冲区,在再次读取之前要重置缓冲区
// 注:不会改变缓冲区的数据,只是重置缓冲区的主要索引值
buff.clear();
}catch (Exception e){
e.printStackTrace();
}
}
【nio服务器端如何实现非阻塞】
服务器上所有Channel需要向Selector注册,而Selector负责监视这些Socket的io状态。当其中任意一个或者多个Channel具有可用的io操作时,该选择器的select()方法返回大于0的整数,这个数字表示选择器上有多少个Channel可以操作。