深入分析JavaIO(二)BIO

前言

上一篇我们对IO进行了一个综合的概述,深入分析 Java IO (一)概述

本篇文章在讲解 BIO之前,我们先来回顾一下这几个概念:同步与异步,阻塞与非阻塞

同步|异步|阻塞|非阻塞

同步

指的是协同步调。既然叫协同,所以至少要有2个以上的事物存在。协同的结果就是:多个事物不能同时进行,必须一个一个的来,上一个事物结束后,下一个事物才开始。

关于同步还需知道两个小的点

1、范围:需要在全局范围内都去同步,只需要在某些关键的点执行同步即可。比如食堂只有一个卖饭窗口,肯定是同步的,一个人买完,下一个人再买。但吃饭的时候也是一个人吃完,下一个人才开始吃吗?当然不是啦。

2、粒度:并不是只有大粒度的事物才有同步,小粒度的事物也有同步。只不过小粒度的事物同步通常是天然支持的,而大粒度的事物同步往往需要手工处理。比如两个线程的同步就需要手工处理,但一个线程里的两个语句天然就是同步的。

异步

就是步调各异。既然是各异,那就是都不相同。所以结果就是:多个事物可以你进行你的、我进行我的,谁都不用管谁,所有的事物都在同时进行中。

注:一定要去体会“多个事物”,多个线程是多个事物,多个方法是多个事物,多个语句是多个事物,多个CPU指令是多个事物。

阻塞

所谓阻塞:指的是阻碍堵塞。它的本意可以理解为由于遇到了障碍而造成的动弹不得。

非阻塞

所谓非阻塞:自然是和阻塞相对,可以理解为由于没有遇到障碍而继续畅通无阻。

BIO

BIO 俗称同步阻塞 IO,一种非常传统的 IO 模型。简单来说,在服务器中BIO是一个连接由一个专门的线程来服务的工作模式。就像餐厅里来一个客人就给这个客人安排一个专用服务员,这个服务员就只服务这一个客人直到他离开为止。

实例图:

image-20210801175804166

BIO即为阻塞IO的意思,大家看下面的代码,

 public class BIOServer {
 ​
     public static void main(String[] args) throws IOException {
         //创建线程池
         ExecutorService executorService = Executors.newCachedThreadPool();
         //创建serverSocket
         ServerSocket serverSocket = new ServerSocket(8888);
         System.out.println("服务端已启动,端口号为8888...");
         while (true){
             System.out.println("线程的信息 id="+Thread.currentThread().getId()+ " 名称="+Thread.currentThread().getName());
             System.out.println("等待连接...");
             //监听
             final Socket accept = serverSocket.accept();
             System.out.println("有一个客户端连接...");
             executorService.execute(new Runnable() {
                 @Override
                 public void run() {
                     handler(accept);
                 }
             });
         }
     }
 ​
     public static void handler(Socket socket){
         try {
             byte[] bytes = new byte[1024];
             //获取输入流
             InputStream inputStream = socket.getInputStream();
             while (true){
                 System.out.println("线程的信息 id="+Thread.currentThread().getId()+ " 名称="+Thread.currentThread().getName());
                 System.out.println("read...");
                 int read = inputStream.read(bytes);
                 if(read!=-1){
                     //输出客户端发送的数据
                     System.out.println(new String(bytes,0,read));
                 }else {
                     break;
                 }
             }
         }catch (Exception e){
             e.printStackTrace();
         }finally {
             try {
                 socket.close();
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }
 ​
 }
复制代码

启动服务,控制台输出:

 服务端已启动,端口号为8888...
 线程的信息 id=1 名称=main
 等待连接...
复制代码

到cmd命令行中:输入telnet 127.0.0.1 8888 回车;

 服务端已启动,端口号为8888...
 线程的信息 id=1 名称=main
 等待连接...
 有一个客户端连接...
 线程的信息 id=1 名称=main
 等待连接...
 线程的信息 id=13 名称=pool-1-thread-1
 read...
复制代码

按住ctrl + ] 回车;输入数据send 100ok,例如:

在这里插入图片描述

控制台输出:

 服务端已启动,端口号为8888...
 线程的信息 id=1 名称=main
 等待连接...
 有一个客户端连接...
 线程的信息 id=1 名称=main
 等待连接...
 线程的信息 id=13 名称=pool-1-thread-1
 read...
 100ok
 线程的信息 id=13 名称=pool-1-thread-1
 read...
复制代码

同样再启动一个命令行窗口,输入telnet 127.0.0.1 8888 回车;控制台的信息:

 服务端已启动,端口号为8888...
 线程的信息 id=1 名称=main
 等待连接...
 有一个客户端连接...
 线程的信息 id=1 名称=main
 等待连接...
 线程的信息 id=13 名称=pool-1-thread-1
 read...
 100ok
 线程的信息 id=13 名称=pool-1-thread-1
 read...
 有一个客户端连接...
 线程的信息 id=1 名称=main
 等待连接...
 线程的信息 id=14 名称=pool-1-thread-2
 read...
复制代码

按住ctrl + ] 回车;输入数据 200ok,控制台信息如下:

 服务端已启动,端口号为8888...
 线程的信息 id=1 名称=main
 等待连接...
 有一个客户端连接...
 线程的信息 id=1 名称=main
 等待连接...
 线程的信息 id=13 名称=pool-1-thread-1
 read...
 100ok
 线程的信息 id=13 名称=pool-1-thread-1
 read...
 有一个客户端连接...
 线程的信息 id=1 名称=main
 等待连接...
 线程的信息 id=14 名称=pool-1-thread-2
 read...
 200ok
 线程的信息 id=14 名称=pool-1-thread-2
 read...
复制代码

存在的问题

  • 只要没有客户端连接上服务器,accept方法就一直不能返回,这就是阻塞;对应的读写操作道理也一样,想要读取数据,必须等到有数据到达才能返回,这就是阻塞。
  • 服务端对于每个请求都要建立一个独立的线程。
  • 并发数大时,要创建大量的线程,系统资源占用大。
  • 建立连接后,如果当前线程没有数据可读,则线程就阻塞在read上,造成线程资源的浪费。

结尾

我是一个正在被打击还在努力前进的码农。如果文章对你有帮助,记得点赞、关注哟,谢谢!