前言
上一篇我们对IO
进行了一个综合的概述,深入分析 Java IO (一)概述
本篇文章在讲解 BIO之前,我们先来回顾一下这几个概念:同步与异步,阻塞与非阻塞。
同步|异步|阻塞|非阻塞
同步
指的是协同步调。既然叫协同,所以至少要有2个以上的事物存在。协同的结果就是:多个事物不能同时进行,必须一个一个的来,上一个事物结束后,下一个事物才开始。
关于同步还需知道两个小的点
:
1、范围
:需要在全局范围内都去同步,只需要在某些关键的点执行同步即可。比如食堂只有一个卖饭窗口,肯定是同步的,一个人买完,下一个人再买。但吃饭的时候也是一个人吃完,下一个人才开始吃吗?当然不是啦。
2、粒度
:并不是只有大粒度的事物才有同步,小粒度的事物也有同步。只不过小粒度的事物同步通常是天然支持的,而大粒度的事物同步往往需要手工处理。比如两个线程的同步就需要手工处理,但一个线程里的两个语句天然就是同步的。
异步
就是步调各异。既然是各异,那就是都不相同。所以结果就是:多个事物可以你进行你的、我进行我的,谁都不用管谁,所有的事物都在同时进行中。
注:一定要去体会“多个事物”,多个线程是多个事物,多个方法是多个事物,多个语句是多个事物,多个CPU指令是多个事物。
阻塞
所谓阻塞:指的是阻碍堵塞。它的本意可以理解为由于遇到了障碍而造成的动弹不得。
非阻塞
所谓非阻塞:自然是和阻塞相对,可以理解为由于没有遇到障碍而继续畅通无阻。
BIO
BIO 俗称同步阻塞 IO,一种非常传统的 IO 模型。简单来说,在服务器中
BIO
是一个连接由一个专门的线程来服务的工作模式。就像餐厅里来一个客人就给这个客人安排一个专用服务员,这个服务员就只服务这一个客人直到他离开为止。
实例图:
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上,造成线程资源的浪费。
结尾
我是一个正在被打击还在努力前进的码农。如果文章对你有帮助,记得点赞、关注哟,谢谢!
近期评论