同步/异步,阻塞/非阻塞
可参考:https://zhuanlan.zhihu.com/p/23488863
- 同步:如果有多个任务需要处理,这些任务必须逐个处理,一个任务的执行会导致整个流程的等待
- 异步:如果有多个任务需要处理,这些任务可以并发处理,一个任务的执行不会导致整个流程的等待
- 阻塞:某个任务执行,他发出一个请求,但是由于条件不满足,导致一直等待直到条件满足
- 非阻塞:某个人物执行,他发出一个请求,条件不满足则返回一个标志告知,不会一直等待
因此同步/异步和阻塞/非阻塞没有必联的关系,至于为什么有时候会感觉两者有联系呢?是因为java中为了使多线程同步,会用上锁的方式锁住某一共享资源,等待锁的线程就被阻塞了,感觉上为了同步就有了阻塞。其实不然,因为同步/异步和阻塞/非阻塞概念产生的前提是不一样的,前两者是多任务,后者是单任务执行过程中遇到阻碍
可参考 java 同步/异步IO和阻塞/非阻塞IO 关系和概念解析
java中NIO跟IO的区别
IO | NIO |
---|---|
面向流(单向) | 面向缓冲区(双向) |
阻塞式 | 非阻塞式 |
无 | 选择器 |
NIO详解
缓冲区
- 在java nio中负责数据存取。缓冲区就是数组,用于储存不同数据类型的数据,根据类型不同,提供能相应的缓冲区(boolean除外),缓冲区的管理方式几乎一致,通过allocate()来获取缓冲区
- ByteBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- floatBuffer
- doubleBuffer
- charBuffer
- 缓冲区的两个核心方法:
- put():存入数据到缓冲区
- get():获取缓冲区中的数据
- 缓冲区的四个核心属性
- capacity:容量,表示缓冲区中最大存储数据的容量,一旦声明不允许改变;
- limit:界限,表示缓冲区中可以操作数据的大小,(limit后的数据不能进行读写)
- position:位置,表示缓冲区中正在操作数据的位置。
- mark:标记,表示记录当前position的位置,可以通过reset恢复到mark的位置;
- mark<position<limit<capacity
- 缓冲区其他方法
- flip:切换成读取模式,实际上是修改limit为当前position,然后修改position为0;
- rewind:重新读取,limit不变,position修改成0;
flip跟rewind的区别就是,limit会不会改变; - clear:清空缓冲区,但是缓冲区中的数据依然存在,但是处于被遗忘状态,意思就是关于数据的缓冲区位置指针重置了;
- hasRemaining:判断缓冲区中是否还有数据;
- remaining:返回缓冲区剩余数据的长度 limit-position
- 直接缓冲区与非直接缓冲区
- 非直接缓冲区:通过allocate方法分配缓存区,将缓冲区建立在JVM的内存中
- 直接缓冲区:通过allocatedirect方法分配直接缓冲区,将缓冲区建立在物理内存中;
- isDirect可以判断是否直接缓冲区;
通道
- 通道:用于源节点与目标节点的连接。在java NIO中负责缓冲区中数据的传输。Channal本身不储存数据,因此需要配合缓冲区进行传输;
- 通道的主要实现类
- java.nio.channels.Channel接口:
- FileChannel
- SocketChannel
- serverSocketChannel
- DatagramChannel
- java.nio.channels.Channel接口:
- 获取通道
- java针对支持通道的类提供了getChannel方法
- FileInputStream/FileOutputStream
- RandomAccessFile
- Socket
- serversocket
- DatagramSocket
- 在jdk7中的nio2针对各个通道提供能静态方法open();
- 在jdk7中的nio2的Files工具类的newByteChannel();
- java针对支持通道的类提供了getChannel方法
- 示例
- 使用流获取channel
- 使用open方法
- 同2类似开启通道,都是使用物理内存直接缓冲区,只不过这里是通道直接传输;
- 使用流获取channel
- 分散与聚集
- 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
- 聚集写入(gathering Writes):将多个缓冲区的数据聚集到通道中;
- 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
- 字符集
- 编码:字符串到字节数组
- 解码:字节数组到字符串
其中框出来的需要注意,不管是编码还是解码都需要将变成读模式,不然没有输出;
网络传输
选择器
@Test
public void client() throws IOException {
SocketChannel channel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//指定为非阻塞模式
channel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(new Date().toString().getBytes());
buffer.flip();
channel.write(buffer);
buffer.clear();
channel.close();
}
@Test
public void server() throws IOException {
//1.获取通道
ServerSocketChannel channel = ServerSocketChannel.open();
//2.切换非阻塞
channel.configureBlocking(false);
//3.绑定端口
channel.bind(new InetSocketAddress(9898));
//4.获取选择器
Selector selector = Selector.open();
//5.注册选择器并指定监听事件
channel.register(selector, SelectionKey.OP_ACCEPT);
//6.轮询获取选择器上已经准备就绪的事件
while(selector.select() > 0) {
//7.获取已经准备就绪的事件迭代器
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()) {
//8.获取事件
SelectionKey selectionKey = iterator.next();
//9.判断事件是否是接收事件
if(selectionKey.isAcceptable()) {
//10.若是接收事件,则才开启连接(可以新建线程)
SocketChannel clientChannel = channel.accept();
//11.切换非阻塞
clientChannel.configureBlocking(false);
//12.注册选择器
clientChannel.register(selector, selectionKey.OP_READ);
}else if(selectionKey.isReadable()) {
//13.获取当前选择器上读就绪的状态的通道
SocketChannel clientChannel = (SocketChannel)selectionKey.channel();
//14.读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
while((len = clientChannel.read(buffer)) != -1) {
buffer.flip();
System.out.println(new String(buffer.array(),0,len));
buffer.clear();
}
}
//15.取消选择键
iterator.remove();
}
}
}