Netty、Redis、Zookeeper高并发实战
- 趁热打铁,继续看书学习~
基础理论
Java中的NIO
- Java中的nio(New IO) 不是 操作系统层面的(Non-block IO), 而是(multi-IO 多路复用IO)
流程
- 这里以read系统调用为例,先看下一个完整输入流程的两个阶段:
- 等待数据准备好。
- 从内核向进程复制数据。
- 如果是read一个socket(套接字),那么以上两个阶段的具体处理流程如下:
- 第一个阶段,等待数据从网络中到达网卡。当所等待的分组到达时,它被复制到内核中的某个缓冲区。这个工作由操作系统自动完成,用户程序无感知。
- 第二个阶段,就是把数据从内核缓冲区复制到应用进程缓冲区。
Java NIO实践
https://mp.weixin.qq.com/s/rsvAmmoJiseEmjChI95m6Q
BIO
BIO两处阻塞
先知道原先的BIO为啥不好,才能体会到NIO的好
- Server实现 https://github.com/Draymonders/Java_NIO/blob/master/src/main/java/bio/ServerDemo.java
- Client实现 https://github.com/Draymonders/Java_NIO/blob/master/src/main/java/bio/ClientDemo.java
运行代码就可以知道,总共产生两次阻塞
- 第一个是在
ServerSocket.accept()等待请求建立 - 第二个是在
ServerSocket.getInputStream()获取请求的输入流
BIO处理客户端并发请求
- 支持并发的Server实现 https://github.com/Draymonders/Java_NIO/blob/master/src/main/java/bio/ServerConcurrencyDemo.java
多线程BIO服务器虽然解决了单线程BIO无法处理并发的弱点,但是也带来一个问题:如果有大量的请求连接到我们的服务器上,但是却不发送消息,那么我们的服务器也会为这些不发送消息的请求创建一个单独的线程,那么如果连接数少还好,连接数一多就会对服务端造成极大的压力。所以如果这种不活跃的线程比较多,我们应该采取单线程的一个解决方案,但是单线程又无法处理并发,这就陷入了一种很矛盾的状态,于是就有了NIO。
NIO 入门
- 方案一 https://github.com/Draymonders/Java_NIO/blob/master/src/main/java/nio/ServerDemo1.java
方案一将建立链接与数据读取InputStream都进行了非阻塞的处理, 但是有问题的是, 数据非阻塞做了处理后,如果客户端不是刚建立完链接就发送数据,那么服务端是无法读取到客户端的发来的数据的
- 方案二 https://github.com/Draymonders/Java_NIO/blob/master/src/main/java/nio/ServerDemo1.java
方案二对建立的每个链接都加入到了List里面,因此可以一边判断是否有链接要建立, 另一边对所有建立的链接进行数据的读取操作
Java中 BIO和NIO最本质的区别
- BIO阻塞,NIO非阻塞
- BIO对于每个网络链接,都需要开辟一个线程去处理相应的请求的输入与响应;(但是对于客户端来说,由于发送数据不是理解的,因此线程会block,也因此大并发场景下,建立了很多线程,系统在线程间上下文切换代价较高)
- NIO则用
Selector一个线程去处理一堆请求的输入与响应
Buffer
属性
-
capcity容量, 比如IntegerBuffercapacity=100代表可以存入100个int -
position当前指针位置 -
limitlimit即可读可写的最大范围 -
mark缓存标记position位置
方法
-
allocate创建一个缓存区 -
put和get分别在写模式下写入和在读模式下读入 -
flip写模式转换为读模式 -
clear读模式转换为写模式 -
rewindposition置为0,从头开始读 -
mark代表记录一个临时position, 而reset代表还原那个临时的position
Channel
最主要的有四种, FileChannel, SocketChannel, ServerSocketChannel, DatagramChannel
Selector
有四种监听事件OP_READ, OP_WRITE, OP_ACCEPT, OP_CONNECT 分别是可读、可写、接收、连接
Reactor
之前的是对于一个请求,新开一个线程处理。
- Reactor的流程则是 对于多个请求,只需要一个reactor(因为可以调用系统底层的select/poll/epoll), 然后也只需要一个handler (因为是非阻塞,所以每次执行很快)
并发
阻塞等待线程
主线程需要等待线程A,线程B执行完后才能执行. 因此可以可以使用join()
Thread a = new Thread("A");
Thread b = new Thread("B");
a.start(); b.start();
a.join(); b.join();
FutureTask
上述join无法获取到线程a和b是否执行成功,仅仅是等待线程a和b执行退出(可能正常退出,也可能异常退出),并且无法拿到值
Callable<Boolean> aJob = new AJob();//③
FutureTask<Boolean>aTask =
new FutureTask<>(aJob);
aJob.get();
// 同理
bJob.get();