Java basics: non-blocking IO (NIO)

Source: Internet
Author: User

Java basics: non-blocking IO (NIO)

 

 

Introduction

 

NIO (New IO) is introduced in JDK1.4 to improve the IO speed. Note that JavaNIO is not fully non-Blocking IO (No-Blocking IO), because some channels (such as FileChannel) can only run in Blocking mode, other channels can be selected between blocking and non-blocking channels.

Despite this, we are still used to regard Java NIO as non-blocking IO, while the IO class library for stream (byte/character) introduced earlier is non-blocking. For details, the differences are as follows:

IO

NIO

Stream oriented)

Buffer oriented)

Blocking IO)

Non-blocking (Non-blocking IO)

None

Selectors)

But remember, there are no advantages or disadvantages between the two. NIO is an extension of java io. It is useful for different scenarios.

 

Stream-oriented and buffer-oriented

The first major difference between Java NIO and IO is that IO is stream-oriented and NIO is buffer-oriented. JavaIO stream orientation means that one or more bytes are read from the stream each time until all bytes are read, and they are not cached anywhere. In addition, it cannot move data in the stream before and after. If you need to move the data read from the stream before and after, you must first cache it to a buffer zone. The buffer-oriented method of Java NIO is slightly different. The data is read to a buffer that will be processed later. When necessary, it can be moved before and after the buffer. This increases the flexibility in the processing process. However, you also need to check whether the buffer contains all the data you need to process. In addition, make sure that when more data is read into the buffer, do not overwrite the unprocessed data in the buffer.

 

Blocking and non-blocking IO

Various Java IO streams are blocked. This means that when a thread calls read () or write (), the thread is blocked until some data is read or completely written. This thread can no longer do anything during this period. The non-blocking mode of Java NIO enables a thread to send requests from a channel to read data, but it can only obtain currently available data. If no data is available, you won't get anything. Instead of keeping the thread congested, the thread can continue to do other things until the data becomes readable. This is also true for non-blocking writes. A thread requests to write some data to a channel, but does not need to wait for it to write completely. This thread can also do other things. Threads usually use the idle time of non-blocking IO to execute IO operations on other channels, so a separate thread can now manage multiple input and output channels ).

 

Selectors)

Java NIO selector allows a single thread to monitor multiple input channels. You can register multiple channels and use one selector. Then, you can use a separate thread to select a channel: there are input that can be processed in these channels, or select the channel to be written. This selection mechanism makes it easy for a single thread to manage multiple channels.

 

The three most critical concepts of Java NIO are channel, buffer, and selector:

I. Channel)

The Java NIO channel is similar to a stream, but it is somewhat different:

The n channel is bidirectional and readable and writable, while the stream read/write is unidirectional.

N-channel can be read and written asynchronously.

N regardless of read/write, the channel can only interact with the Buffer.

 

Implementation of the most important channels in JavaNIO:

U FileChannel: reads and writes data from a file.

U memory ramchannel: reads and writes data in the network through UDP.

U SocketChannel: reads and writes data in the network through TCP.

U ServerSocketChannel: You can listen to new TCP connections, like Web servers. A SocketChannel is created for each new connection.

 

The following is an example of writing data to a file through FileChannel:

 

public class Test {   public static void main(String[] args) throws IOException  {       File file = new File(test.txt);       FileOutputStream os = new FileOutputStream(file);       FileChannel channel = os.getChannel();       ByteBuffer buffer = ByteBuffer.allocate(1024);       String str = hello,jiyiqin;       buffer.put(str.getBytes());       buffer.flip();       channel.write(buffer);       channel.close();       os.close();   } }

 

Note: The preceding example has two key points: one is to use the FileOutputStream file output stream to obtain the channel, the old IO class library (or the stream-oriented IO class library) fileInputStream/FileOutputStream and RandomAccessFile are modified to generate FileChannel. However, character-oriented stream Reader/Writer cannot generate a channel. In addition, before writing buffer data to the channel, you must call the flip method of the buffer zone to convert it to the Read mode so that the channel can read data from the buffer zone.

 

Ii. Buffer)

The Buffer in Java NIO is used to interact with the NIO channel. Data is read from the channel into the Buffer or written from the Buffer to the channel. When writing data to the buffer, the buffer records the amount of data written. To read data, you must use the flip () method to switch the Buffer from the write mode to the Read mode. In Read mode, you can read all data previously written to the buffer. Once all the data is read, you need to clear the buffer so that it can be written again. There are two ways to clear the buffer: Call the clear () or compact () method. The clear () method clears the entire buffer. The compact () method only clears read data. Any unread data is moved to the beginning of the buffer zone, and the newly written data is placed behind the unread data in the buffer zone.

A buffer is essentially a memory that can write data and then read data from it. This memory is encapsulated into NIO Buffer objects and provides a set of methods for convenient access to this block of memory.

The following is an example of reading data from the file channel FileChannel:

 

RandomAccessFile aFile = newRandomAccessFile (data/nio-data.txt, rw); FileChannel inChannel = aFile. getChannel (); ByteBuffer buf = ByteBuffer. allocate (48); int bytesRead = inChannel. read (buf); while (bytesRead! =-1) {buf. flip (); // read the buffer while (buf. hasRemaining () {System. out. print (char) buf. get (); // read one byte at a time} buf. clear (); bytesRead = inChannel. read (buf);} aFile. close ();

 

 

3. Selector)

Selector allows a single thread to process multiple channels. If your application opens multiple connections (channels), but the traffic for each connection is low, it is very convenient to use Selector. For example, in a chat server.

The advantage of using a single thread to process multiple Channels is that fewer threads are needed to process Channels. In fact, you can use only one thread to process all the channels. For the operating system, context switching between threads is costly, and each thread occupies some system resources (such as memory ). Therefore, the fewer threads used, the better.

However, remember that the performance of modern operating systems and CPUs is getting better and better in terms of multitasking, so the overhead of multithreading becomes smaller and smaller over time. In fact, if a CPU has multiple kernels, the CPU capability may be wasted if no multi-task is used. In any case, the discussion about that design should be put in another article. Here, it is sufficient to know that Selector can process multiple channels.

Use a reprinted figure to display three channels in a single thread using a Selector:

 

Step 1: Create Selector

Create a Selector by calling the Selector. open () method, as shown below:

 

Selectorselector = Selector.open(); 

 

Step 2: register a channel with Selector

To use the Channel and Selector together to realize the dream of a single thread to process multiple channels, the channel must be registered to the selector. You can use the SelectableChannel. register () method as follows:

 

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

 

The first code sets the channel to non-blocking mode, and the second code registers the channel with selector. The second parameter of the register () method. This is an "interest set", which means what events are interested in listening to channels through Selector. You can listen to four different types of events:

SelectionKey. OP_CONNECT

SelectionKey. OP_ACCEPT

SelectionKey. OP_READ

SelectionKey. OP_WRITE

Note:When used with Selector, the Channel must be in non-blocking mode. This means that FileChannel and Selector cannot be used together, because FileChannel cannot be switched to non-blocking mode (because of its own characteristics, note that the file mentioned here is different from the file in Linux, files in Linux can be disk files, printer devices, NICS, and so on, but the files mentioned here are only disk files ). All socket channels are supported.

 

Step 3: block the monitoring Channel

Once one or more channels are registered with Selector, several overloaded select () methods can be called. These methods return the channels that are ready for the events you are interested in (such as connection, acceptance, read, or write. In other words, if you are interested in the "Read-ready" channel, the select () method will return those channels with read events ready.

The select () method is as follows:

 

int select()int select(long timeout)int selectNow()

 

Select () blocking to at least one channel is ready for the event you registered.

Select (long timeout) is the same as select (), except for the maximum blocking timeout Millisecond (parameter ).

SelectNow () is not blocked and will return immediately no matter which channel is ready (Note: This method performs a non-blocking selection operation. If no channel becomes selectable since the previous selection operation, this method returns zero directly .).

 

Step 4: traverse the selectedKeys () Access readiness Channel

Once the select () method is called and the return value indicates that one or more channels are ready, you can call the selectedKeys () method of selector, access the ready channel in selected key set. As follows:

Set selectedKeys = selector. selectedKeys ();

You can traverse the selected key set to access the ready channel.

 

The following is an example of a complete combination of Channel and Selector:

 

Selector selector = Selector.open(); channel.configureBlocking(false); SelectionKey key =channel.register(selector, SelectionKey.OP_READ); while(true) {   intreadyChannels = selector.select();  if(readyChannels == 0) continue;   SetselectedKeys = selector.selectedKeys();  Iterator keyIterator = selectedKeys.iterator();  while(keyIterator.hasNext()) {    SelectionKey key = keyIterator.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    }  } } 

 

Selector + socket channel:

Socket channels include SocketChannel, ServerSocketChannel, and DatagramChannel.

SocketChannel is a channel connected to a TCP network socket. You can create one in two ways:

1) Open a SocketChannel and connect to a server on the Internet.

2) create a SocketChannel when a new connection reaches the ServerSocketChannel.

ServerSocketChannel listens for new connections through the ServerSocketChannel. accept () method. When the accept () method returns, it returns a SocketChannel containing the new connection. Therefore, the accept () method is blocked until a new connection arrives. Mongoramchannel is a channel that can send and receive UDP packets. Because UDP is a connectionless network protocol, it cannot be read or written as other channels do. It sends and receives data packets.

The combination of the socket channel and Selector, because of the multiplexing feature (event-driven) of the Selector and the non-blocking feature of the socket channel, it can effectively solve the situation that the processing of client requests will consume a lot of thread resources in a high-concurrency environment.

(1) traditional synchronous blocking I/O (network Socket programming Socket): A separate thread must be allocated for processing each request connection on the client, because you need to monitor whether data is read and written at any time, and when reading and writing data, it is blocked, so even if there is no data, it will always block waiting. This obviously wastes a lot of CPU and thread resources.

(2) The combination of multiplexing selector and non-blocking Socket channels: not only can one thread be used to monitor the network connections established between multiple clients, but also can read and write data, once the data is not ready, return immediately without blocking (although the data is usually ready once read/write is executed ). Therefore, in scenarios with high concurrency, this method greatly saves CPU and thread (memory) resources. For details, refer to my article.

 

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.