同步与阻塞

同步和异步是针对应用程序和内核的交互而言的。

  • 同步:执行一个操作之后,进程触发IO操作并等待(阻塞)或者轮询的去查看IO的操作(非阻塞)是否完成,等待结果,然后才继续执行后续的操作。

  • 异步:执行一个操作后,可以去执行其他的操作,然后等待通知再回来执行刚才没执行完的操作。

    非阻塞是针对于进程在访问数据的时候,根据IO口的状态返回不同的状态值。阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。

  • 阻塞:进程给CPU传达一个任务之后,一直等待CPU处理完成,然后才执行后面的操作。

  • 非阻塞:进程给CPU传达任我后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。(轮询)

    同步异步是结果,阻塞非阻塞是手段。

Java IO的各种流是同步阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 当然,他还有一个更加重要的特性是,多路复用IO。

Java NIO的同步非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。

之前使用futrueModel的就是类似异步IO模型。异步一定是非阻塞的。

概述

Java的NIO有三个核心部分:Channels、Buffers、Selectors

Channel 和 Buffer

Channel有四个类型:

文件 UDP TCP-client TCP-server
FileChannel DatagramChannel SocketChannel ServerSocketChannel

Buffer有八个实现类型,对应八种基本数据类型除了布尔型的七种,以及一个映射类型:

ByteBuffer IntBuffer CharIntBuffer FloatIntBuffer
ShortBuffer LongIntBuffer DoubleIntBuffer MappedByteBuffer

可以将一个Channel看作一个流,而buffer和channel联合起来就是一个带有缓冲区域的流。和stream不同的是,stream的方向是单向的,但是buffer和channel之间的数据传输是双向的,数据既可以从channel到buffer,也可以从buffer到channel。

Selector

Selector是一个管理器,可以管理多个channel。在线程中使用Selector,将Channel注册到Selector中,在channel中有事件就绪,就会将调用select方法,获取事件,响应事件。

Channel

Channel 是一个接口,其常用的实现类有下面四个:

文件 UDP TCP-client TCP-server
FileChannel DatagramChannel SocketChannel ServerSocketChannel

一个Channel的使用实例。

package com.xzj;import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;//   @ author :zjxu     time:2019/1/8
public class Main {final static String PATH = "/Users/thisxzj/IDEAProject/Revise/NIO/src/index.txt";public static void main(String[] args) throws IOException {RandomAccessFile file = new RandomAccessFile(PATH, "rw");FileChannel fileChannel = file.getChannel();ByteBuffer byteBuffer = ByteBuffer.allocate(64);int length = fileChannel.read(byteBuffer);if (length != -1) {System.out.println("length = " + length );byteBuffer.flip();          //反转bufferwhile (byteBuffer.hasRemaining()) {System.out.print((char) byteBuffer.get());}//byteBuffer.rewind();//while (byteBuffer.hasRemaining()) {//    System.out.print((char) byteBuffer.get());//}byteBuffer.clear();}fileChannel.close();}
}

运行结果:

length = 12
Hello World!

Buffer

Buffer是一个抽象类,他有有八个实现类型,对应八种基本数据类型除了布尔型的七种,以及一个映射类型:

ByteBuffer IntBuffer CharIntBuffer FloatIntBuffer
ShortBuffer LongIntBuffer DoubleIntBuffer MappedByteBuffer

buffer的基本用法

  • 写入数据到Buffer
  • 调用flip()方法
  • 从Buffer中读取数据
  • 调用clear()方法或者compact()方法

buffer的内部

buffer内部是一个对应类型的数组,这个数字有四个index,这四个index在数据的存取过程中有自己作用。属性解释:

  1. capacity:缓冲区数字的总长度。
  2. pasition:下一个要操作的数据元素的位置。
  3. limit:缓冲区不可操作的下一个位置的位置。
  4. mark:记录position前一个位置,default:-1(也就是不存在)。

使用方法:

  1. 创建方法:ByteBuffer.allocate(n) //创建一个长度为n的Byte的缓冲区。这个时候的 capacity 和 limit的大小都是数组的长度;position的大小是0,数组的首端。
  2. 在写入了数据之后,position会变为数组没有存储数据的位置的一个位置。
  3. 使用byteBuffer.flip方法,limit = position,position = 0,然后就可以正确的读取这鞋数据,并且将数据发送出去。
  4. 然后使用 byteBuffer.clear方法,将会到刚刚创建的状态。
  5. 关于 mark 方法 ,使用mark方法的时候,mark会记录position - 1 的数据大小。当再次使用reset的时候,position将不会还原,会回到mark的值。

几个方法:

public Buffer flip() {limit = position;position = 0;mark = -1;return this;
}······
public Buffer clear() {position = 0;limit = capacity;mark = -1;return this;
}······
public Buffer rewind() {position = 0;mark = -1;return this;
}······
public final Buffer mark() {mark = position;return this;
}······
public Buffer reset() {int m = mark;if (m < 0)throw new InvalidMarkException();position = m;return this;
}

flip方法

反转buffer,反转缓冲区。首先将限制设置为当前位置,然后将位置设置为 0。通常情况下,在从写入状态转向读取状态的时候调用flip方法。

clear、compact方法

清除方法,清除整个缓冲区,相当于是再次初始化。和这个类似的还有一个compact方法。clear的作用是将全部缓冲区域清除,如果缓冲区中的数据全部使用结束,那么使用clear是没有问题的。

如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先写些数据,那么就使用compact()方法。compact()方法将所有未读的数据拷贝搬移Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。也就是将没有使用过的数据搬移到初始端,然后,在原先数据的后面接着写入新的数据。

rewind 方法

无论是在读还是写的模式下,都从头开始。如果是读的模式,使用这个方法,写入新的数据将会覆盖原来数据的。如果是读取的话,将会重复的将原先读过的数据在读一遍。

如将channel中的注释打开,那么执行的结果就是:

length = 12
Hello World!Hello World!

这就体现了在读取的时候rewind的作用。

mark()与reset()方法

通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。

例如,先mark一下,然后经过读或者是写,之后,使用reset可以将position返回到使用mark的时候的那个mark值。但是flip、clear、compact、rewind、reset都会将mark标记回归负一,也就是取消掉。在使用reset的时候,必须要有一个mark,否则会exception。

buffer的使用

创建

使用Buffer抽象类的实现类的静态方法来创建,例如:

//创建一个对应的Buffer的对象,其最大容量为128。
XXXBuffer xxxBuffer = XXXBuffer.allocate(128);

写数据

从channel中:

int readLength = inChannel.read(xxxBuffer);

使用channel的read方法,将channel中的数据传入xxxBuffer中。

使用put方法:

xxxBuffer.put(new byte[]{' ', 'x', ' ', 'z', ' ', 'j', ' ', '!'});

使用buffer对象的put方法,将put方法的参数,传入的到buffer中。这个参数可以是这个buffer的类型的值、数组。还可以是另一个同类型的buffer,这个参数的本质也是一个数组。

读取数据

从Buffer读取数据到Channel:

int writeLength = inChannel.write(xxxBuffer);

使用get方法获取buffer中的数据:

xxxBuffer.get();           //获取的是一个xxx类型的数据

其他

可以使用equare 和 compareTo来比较两个buffer。

多buffer操作

使用多个buffer,对应一个channel。

scatter

使用buffer的数组从channel中获取信息的时候,将会按照数组中buffer的顺序,将channel中的信息,保存在buffer中。

package com.xzj;import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;//   @ author :zjxu     time:2019/1/8
public class Main {final static String PATH = "/Users/thisxzj/IDEAProject/Revise/NIO/src/index.txt";public static void main(String[] args) throws IOException {RandomAccessFile file = new RandomAccessFile(PATH, "rw");FileChannel fileChannel = file.getChannel();ByteBuffer byteBuffer1 = ByteBuffer.allocate(10);ByteBuffer byteBuffer2 = ByteBuffer.allocate(100);ByteBuffer[] byteBuffer = new ByteBuffer[]{byteBuffer1, byteBuffer2};long length = fileChannel.read(byteBuffer);if (length != -1) {System.out.println("length = " + length);byteBuffer2.flip();          //反转bufferwhile (byteBuffer2.hasRemaining()) {System.out.print((char) byteBuffer2.get() );}byteBuffer2.clear();}fileChannel.close();}
}

index.txt中保存的文本:

Hello World!Hello World!

程序的输出:

length = 24
d!Hello World!

很明显,第一个长度为10的数组中,保存了前面十个字符。后面长度为100的保存了后面的十四位。在移动下一个buffer前,必须填满当前的buffer。

gather

同样的,channel在获取数字的时候,可以有这样的数组的机制。但是需要注意的是,只有position和limit之间的数字会被填充进去。所以和前面的一样,需要使用filp更改来将数据刷新进去。在满的时候,或者是将队尾的空格输入进去的时候,使用rewind。

package com.xzj;import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;//   @ author :zjxu     time:2019/1/8
public class Main {final static String PATH = "/Users/thisxzj/IDEAProject/Revise/NIO/src/index.txt";public static void main(String[] args) throws IOException {RandomAccessFile file = new RandomAccessFile(PATH, "rw");FileChannel fileChannel = file.getChannel();ByteBuffer byteBuffer1 = ByteBuffer.allocate(10);ByteBuffer byteBuffer2 = ByteBuffer.allocate(10);byteBuffer1.put("0123456789".getBytes());byteBuffer1.rewind();byteBuffer2.put("ABCDEFGHIJ".getBytes());byteBuffer2.rewind();ByteBuffer[] byteBuffer = new ByteBuffer[]{byteBuffer1, byteBuffer2};long length = fileChannel.write(byteBuffer);System.out.println(length);fileChannel.close();}
}

index.txt中的文本:

0123456789ABCDEFGHIJ

控制台信息:

20

通道间通信

transferFrom

将from中文本信息复制到to中,如果to中文本信息长度比from长,多余的一部分保留。

package com.xzj;import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;//   @ author :zjxu     time:2019/1/8
public class Main {final static String FROM = "/Users/thisxzj/IDEAProject/Revise/NIO/src/fromIndex.txt";final static String TO = "/Users/thisxzj/IDEAProject/Revise/NIO/src/toIndex.txt";public static void main(String[] args) throws IOException {RandomAccessFile fromFile = new RandomAccessFile(FROM, "rw");RandomAccessFile toFile = new RandomAccessFile(TO, "rw");FileChannel fromChannel = fromFile.getChannel();FileChannel toChannel = toFile.getChannel();long position = 0;long count = fromChannel.size();toChannel.transferFrom(fromChannel, position, count);fromChannel.close();toChannel.close();}
}

运行结果:

fromIndex:abcdbfghijkrmnopqrstuvwxyz
toIndex:1234567890123456789012345678901234567890运行之后,from中文本不变,to中的文本变化如下:
toIndex:abcdbfghijkrmnopqrstuvwxyz78901234567890

transferTo

将上面的程序的第21行改为:

       fromChannel.transferTo(position, count, toChannel);

其余的逻辑,方法都不变。

SelectableChannel

和selector配套使用的是selectable接口的实现类,通常是网络接口。所以,NIO是和网络紧密相关的一个IO机制。

  • 有关UDP协议的:DatagramChannel。
  • 有关SCTP协议的:SctpChannel、SctpMultiChannel、SctpServerChannel。
  • 有关TCP协议的:ServerSocketChannel、SocketChannel。
  • 有关管道的:SinkChannel、SourceChannel这两个抽象类定义在java.nio.channels.Pipe类中。
  • Socket和ServerSocket是位于java.net下的两个类。
  • SocketChannel和ServerSocketChannel是位于java.nio.channels下的两个类。

连接关系:

  • 服务器必须先建立ServerSocket或者ServerSocketChannel 来等待客户端的连接。
  • 客户端必须建立相对应的Socket或者SocketChannel来与服务器建立连接。
  • 服务器接受到客户端的连接,再生成一个Socket或者SocketChannel与此客户端通信。

SocketChannel

打开方法

//客户端
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000));

连接方法

//客户端
socketChannel.configureBlocking(false);  //非阻塞模式
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000));

写入方法

//客户端
ByteBuffer writeBuffer = ByteBuffer.allocate(64);
writeBuffer.clear();
writeBuffer.put((new Date().toString()).getBytes());
writeBuffer = writeBuffer.flip();
socketChannel.write(writeBuffer);

读取方法

//客户端
readBuff.clear();
int length = socketChannel.read(readBuff);
readBuff.flip();
System.out.println("received: " +new String(readBuff.array()).substring(0, length));

关闭方法

//客户端
socketChannel.close();

ServerSocketChannel

打开方法

//服务端
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8000));
ssc.configureBlocking(false);

连接方法

//服务端
ssc.configureBlocking(false);               //非阻塞模式
ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8000));

监听连接

通过 ServerSocketChannel.accept() 方法监听新进来的连接。当 accept()方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。因此, accept()方法会一直阻塞到有新连接到达。

通常不会仅仅只监听一个连接,在while循环中调用 accept()方法。如下面的例子:

while(true){SocketChannel socketChannel = serverSocketChannel.accept();//do something with socketChannel...
}

当然,也可以在while循环中使用除了true以外的其它退出准则。

读取方法

//使用一个新建的Socket连接,在这个连接上使用read和write
readBuff.clear();
int length = socketChannel.read(readBuff);
readBuff.flip();
System.out.println("received: " +new String(readBuff.array()).substring(0, length));

关闭方法

//服务端
serverSocketChannel.close();

TCP的使用示例

WebServer

package com.xzj;//   @ author :zjxu     time:2019/1/9import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;public class WebServer {public static void main(String[] args) throws IOException {//创建一个channel并且设定为非阻塞模式ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 8000));serverSocketChannel.configureBlocking(false);//创建selectorSelector selector = Selector.open();//将channel和selector关联起来,并且指定感兴趣的事件是 AcceptserverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//创建读写bufferByteBuffer readBuff = ByteBuffer.allocate(1024);ByteBuffer writeBuff = ByteBuffer.allocate(128);writeBuff.put("enter:".getBytes());writeBuff.flip();while (true) {int nReady = selector.select();Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> it = keys.iterator();while (it.hasNext()) {//轮询SelectionKey key = it.next();it.remove();if (key.isAcceptable()) {// 创建新的连接,并且把连接注册到selector上,而且// 声明这个channel只对读操作感兴趣SocketChannel socketChannel = serverSocketChannel.accept();socketChannel.configureBlocking(false);socketChannel.register(selector, SelectionKey.OP_READ);} else if (key.isReadable()) {SocketChannel socketChannel = (SocketChannel) key.channel();readBuff.clear();int length = socketChannel.read(readBuff);readBuff.flip();System.out.println("received: " + new String(readBuff.array()).substring(0, length-1));key.interestOps(SelectionKey.OP_WRITE);} else if (key.isWritable()) {writeBuff.rewind();SocketChannel socketChannel = (SocketChannel) key.channel();socketChannel.write(writeBuff);key.interestOps(SelectionKey.OP_READ);}}}}
}

WebClient

package com.xzj;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Date;//   @ author :zjxu     time:2019/1/9
public class WebClient {public static void main(String[] args) throws IOException {try {SocketChannel socketChannel = SocketChannel.open();socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000));//创建读写bufferByteBuffer writeBuffer = ByteBuffer.allocate(64);ByteBuffer readBuffer = ByteBuffer.allocate(32);while (true) {writeBuffer.clear();writeBuffer.put((new Date().toString()).getBytes());writeBuffer.flip();socketChannel.write(writeBuffer);readBuffer.clear();socketChannel.read(readBuffer);}} catch (IOException e) {e.printStackTrace();}}
}

DatagramChannel

打开方法

DatagramChannel channel = DatagramChannel.open();

连接方法

channel.socket().bind(new InetSocketAddress(8000));

收发方法

ByteBuffer sendBuffer = ByteBuffer.allocate(64);
ByteBuffer receiveBuffer = ByteBuffer.allocate(64);//接收方法
receiveBuffer.clear();
channel.receive(receiveBuffer);String string =new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date());
//发送方法
sendBuffer.clear();
sendBuffer.put(string.getBytes());
sendBuffer.flip();
channel.send(sendBuffer,new InetSocketAddress(8001));

可以将DatagramChannel“连接”到网络中的特定地址的。由于UDP是无连接的,连接到特定地址并不会像TCP通道那样创建一个真正的连接。而是锁住DatagramChannel ,让其只能从特定地址收发数据。

这里有个例子:

`channel.connect(``new` `InetSocketAddress(``"jenkov.com"``, ``80``));`

当连接后,也可以使用read()和write()方法,就像在用传统的通道一样。只是在数据传送方面没有任何保证。这里有几个例子:

int bytesRead = channel.read(buf);
int bytesWrite = channel.write(buf);

UDP的使用示例

package com.xzj;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;//   @ author :zjxu     time:2019/1/9
public class DatagramTest {public void send() throws IOException {DatagramChannel dChannel = DatagramChannel.open();dChannel.configureBlocking(false);ByteBuffer buffer = ByteBuffer.allocate(1024);Scanner scanner = new Scanner(System.in);while(scanner.hasNext()){String string = scanner.next();buffer.put((new Date().toString()+">>"+string).getBytes());buffer.flip();dChannel.send(buffer, new InetSocketAddress("127.0.0.1", 8989));buffer.clear();}dChannel.close();}public void receive() throws IOException {DatagramChannel datagramChannel = DatagramChannel.open();datagramChannel.configureBlocking(false);datagramChannel.bind(new InetSocketAddress(8989));Selector selector = Selector.open();datagramChannel.register(selector, SelectionKey.OP_READ);while(selector.select()>0){Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey selectionKey = (SelectionKey) iterator.next();if (selectionKey.isReadable()) {ByteBuffer buffer = ByteBuffer.allocate(1024);datagramChannel.receive(buffer);buffer.flip();System.out.println(new String(buffer.array(),0,buffer.limit()));buffer.clear();}}iterator.remove();}}
}

Pipe

JavaNIO中的pipe是两个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。

创建管道

Pipe pipe = Pipe.open();

写入

SinkChannel sinkChannel = pipe.sink();
ByteBuffer byteBuffer = ByteBuffer.allocate(64);String string =new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date());byteBuffer.clear();
byteBuffer.put(string.getBytes());
byteBuffer.flip();sinkChannel.write(byteBuffer);

读取

SourceChannel sourceChannel = pipe.source();
ByteBuffer readBuffer = ByteBuffer.allocate(64);readBuffer.clear();
int length = sourceChannel.read(readBuffer);
System.out.println(new String(readBuffer.array()).substring(0, length - 1));
sourceChannel.close();

Pipe使用示例

package com.xzj;import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
import java.nio.channels.Pipe.SinkChannel;
import java.nio.channels.Pipe.SourceChannel;
import java.text.SimpleDateFormat;
import java.util.Date;//   @ author :zjxu     time:2019/1/9
public class Test {public static void main(String[] args) throws IOException {Pipe pipe = Pipe.open();SinkChannel sinkChannel = pipe.sink();ByteBuffer writeBuffer = ByteBuffer.allocate(64);String string =new SimpleDateFormat("yyyyMMdd HH:mm:ss").format(new Date()) + "\n";writeBuffer.clear();writeBuffer.put(string.getBytes());writeBuffer.flip();sinkChannel.write(writeBuffer);sinkChannel.close();SourceChannel sourceChannel = pipe.source();ByteBuffer readBuffer = ByteBuffer.allocate(64);readBuffer.clear();int length = sourceChannel.read(readBuffer);System.out.println(new String(readBuffer.array()).substring(0, length - 1));sourceChannel.close();}
}

运行结果:

20190109 12:34:14

Selector

使用scelector的好处是,只需要更少的线程来维护通道。,可以使用以恶搞线程处理所有的通道。

创建方法

Selector selector = Selector.open();//selector.isOpen();判断是否打开//selector.close();关闭

注册通道

channel.configureBlocking(false);
SelectionKey readKey = channel.register(selector,SelectionKey.OP_READ);

第二个参数,表示监听的动作:

有四个类型:

事件 符号 key常量
连接就绪 Connect SelectionKey.OP_CONNECT
接收就绪 Accept SelectionKey.OP_ACCEPT
读就绪 Read SelectionKey.OP_READ
写就绪 Write SelectionKey.OP_WRITE

Key

使用key常量将selector和channel中的动作关联起来,并返回一个SelectionKey对象。只要是channel向selector中注册了事件,selector就会跟踪检测这个事件是否发生。这些的key对象就是检测这些事件的句柄。

获取keys

//所有注册到selector的key
Set<SelectionKey> keys = selector.keys();
//相关事件被捕获的key
Set<SelectionKey> selectedKeys = selector.selectedKeys();

如果关闭了与SelectionKey对象关联的Channel对象,或者调用了SelectionKey对象的cancel方法,这个SelectionKey对象就会被加入到cancelled-keys集合中,表示这个SelectionKey对象已经被取消。

cancelled-keys没有对应的方法被获取。

interest集合

int interestSet = key.interestOps();
boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = (interestSet & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT;
boolean isInterestedInRead = (interestSet & SelectionKey.OP_READ) == SelectionKey.OP_READ;
boolean isInterestedInWrite = (interestSet & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE;

用来检测对那些事件感兴趣。其中:

操作类型 描述
OP_READ 1 << 0 读操作
OP_WRITE 1 << 2 写操作
OP_CONNECT 1 << 3 连接socket操作
OP_ACCEPT 1 << 4 接受socket操作

使用interestOps();方法获取的是一个多位二进制数,通过逻辑操作,可以判断是不是在key上有对应的操作。

ready集合

int readySet = key.readyOps();
boolean isAcceptable = (readySet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isConnectable = (readySet & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT;
boolean isReadable = (readySet  & SelectionKey.OP_READ) == SelectionKey.OP_READ;
boolean isWritable =  (readySet & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE;

和上面的获取机制一模一样。此外还可以使用直接获取的方式,获取ready状态。

key.isAcceptable();
key.isConnectable();
key.isReadable();
key.isWritable();

附加对象

通过key生成的channel和seletor:

Channel keyChannel = key.channel();
Selector keySelector = key.selector();

将一个对象添加到key上:

StringBuilder stringBuilder = new StringBuilder("xzj");
//添加上去
key.attach(stringBuilder);
//从key获取
StringBuilder getter = (StringBuilder) key.attachment();

select()

向一个selector中注册多个channel之后,可以使用selector的select方法,该方法的返回值是一个int型,表示的是通道就绪的数量。使用的select()方法是阻塞等待至少一个通道准备就绪,其中可以向其中添加一个最长等待时间,也可以使用selectNow()来表示立即返回。

如下:

selector.select();
selector.select(1000);
selector.selectNow();

遍历

Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator selectedKeysIterator = selectedKeys.iterator();while (selectedKeysIterator.hasNext()) {SelectionKey key = (SelectionKey) selectedKeysIterator.next();if (key.isAcceptable()) {      // a connection was accepted by a ServerSocketChannel} else if (key.isConnectable()) {     // a connection was established with a remote server} else if (key.isReadable()) {        // a channel is ready for reading} else if (key.isWritable()) {        // a channel is ready for writing}//将刚刚访问过的元素删除selectedKeysIterator.remove();
}

weakUp方法

在某一个线程在使用select()方法被阻塞,而无法返回的时候,在同一个select对象上使用weakUp()方法,会使阻塞的select方法返回。

转载于:https://www.cnblogs.com/zjxu97/p/10426429.html

理解Java的NIO相关推荐

  1. 深入java核心_Java核心(五)深入理解BIO、NIO、AIO

    导读:本文你将获取到:同/异步 + 阻/非阻塞的性能区别:BIO.NIO.AIO 的区别:理解和实现 NIO 操作 Socket 时的多路复用:同时掌握 IO 最底层最核心的操作技巧. BIO.NIO ...

  2. Java核心(五)深入理解BIO、NIO、AIO

    导读:本文你将获取到:同/异步 + 阻/非阻塞的性能区别:BIO.NIO.AIO 的区别:理解和实现 NIO 操作 Socket 时的多路复用:同时掌握 IO 最底层最核心的操作技巧. BIO.NIO ...

  3. Java提高班(五)深入理解BIO、NIO、AIO

    导读:本文你将获取到:同/异步 + 阻/非阻塞的性能区别:BIO.NIO.AIO 的区别:理解和实现 NIO 操作 Socket 时的多路复用:同时掌握 IO 最底层最核心的操作技巧. BIO.NIO ...

  4. 【Java基础】从Java语言层面理解BIO,NIO,AIO(二)

    文章目录 零.从网络层面理解BIO,NIO,AIO 一.相关概念 1.什么是socket? 2.IP地址(IP Address) 3.端口(Port) 4.协议(Protocol) 4.1.协议简介 ...

  5. 深入理解 Java 虚拟机(第一弹) - Java 内存区域透彻分析

    来自:好好学java 这篇文章主要介绍Java内存区域,也是作为Java虚拟机的一些最基本的知识,理解了这些知识之后,才能更好的进行Jvm调优或者更加深入的学习,本来这些知识是晦涩难懂的,所以希望能够 ...

  6. 《深入理解java虚拟机》笔记1——Java内存区域与Java对象

    运行时数据区域 JVM载执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程 ...

  7. 深入理解Java虚拟机-Java内存区域透彻分析

    Java虚拟机深入理解系列全部文章更新中- 深入理解Java虚拟机-Java内存区域透彻分析 深入理解Java虚拟机-常用vm参数分析 深入理解Java虚拟机-JVM内存分配与回收策略原理,从此告别J ...

  8. java io nio aio_Java IO、NIO、AIO知识总结

    本文主要讲述下自己对IO的理解,对IO的用法和细则可能没有顾虑到. 本文的理解基于以下几篇文章,他们对各自部分都讲的很细,对我理解IO提供了很大帮助. 该文主要讲解了Java IO的类体系以及他们各自 ...

  9. 【Java】NIO中Selector的select方法源码分析

    该篇博客的有些内容和在之前介绍过了,在这里再次涉及到的就不详细说了,如果有不理解请看[Java]NIO中Channel的注册源码分析, [Java]NIO中Selector的创建源码分析 Select ...

最新文章

  1. rsyslog的配置文件使用方法
  2. 2.0Tonmcat高级配置和jDK的安装配置
  3. Centos环境下部署游戏服务器-软件安装
  4. 编写windows 控件需要注意的几个标签属性(Attribute)
  5. servlet会话技术
  6. Codeforces Round #644 (Div. 3)(A-E)
  7. 什么叫内部银团_MOS管和IGBT管有什么区别?
  8. 菜鸟的LEETCODE的MYSQL做题总结2
  9. FF系列重要Staff全列表 (转自天幻网)
  10. CnOpenData上市公司及子公司名称数据简介
  11. Echarts 图表一些细节设置 lenged 双Y轴等
  12. 墙裂推荐4款js网页烟花特效
  13. 数字经济发展现状_(我的)数字媒体的现状
  14. 3月27日下午上完毛概后是一节体育课,这学期要考篮球和跳绳
  15. 扑克迷如何在2019-03-28的市场中利用十字星进行跑赢大盘
  16. 五种永久免费 内网穿透傻瓜式使用
  17. strchr()函数用法及其详解
  18. formData上传文件(传参附带其他参数)
  19. 阿里云输出计算 天猫商家日处理400万订
  20. 【LEAP模型】能源环境发展及碳排放建模预测及不确定性分析

热门文章

  1. unittest笔记
  2. Hibernate之mappedBy
  3. netcore磊科小企路由器使用测试-nr235p--测试结果令人气愤!!!!
  4. (转)轻量级 UML 建模工具JUDE 介绍(二)
  5. 支付宝支付 第三集:搭建项目及测试(含代码)
  6. 发那科sub_一文搞定FANUC PMC
  7. python制作小工具_利用Python制作翻译小工具
  8. C++知识点39——运算符的重载概念与分数类实现(上)
  9. mysql 逻辑架构
  10. fedora 14安装OFFICE套件