JAVA NIO source code analysis---summary

Through the source code analysis of JAVA NIO in the previous article, some important code implementations have been explored, and the conclusions drawn from the source code analysis are summarized as follows.

1. Sort out the source code analysis process.

1.Selector.open() When obtaining the selector, create a Selector implementation class according to different operating systems. The implementation class creates a data structure PollArrayWrapper for saving channel handles and event types. If it is a Windows system, a pair of mutual The connected socket channel simulation pipeline is used for wake-up, and Linux can create a more efficient epoll mode Selector implementation class for kernel version >= 2.6 and JDK > 1.5u9. And the Linux system can directly use the operating system's pipeline to achieve the wake-up function.

 

2.ServerSocketChannel.register(...) channel registration, create a SelectionKeyImpl object that associates the two according to channel and selector, record it in the set of registered keys, and add socket handles and events to the PollArrayWrapper structure middle. In addition, more threads may need to be created due to the operating system's limit on the maximum number of handles.

 

3.Selector.select(); Before performing the selection operation, first clean up the canceled key set, adjust the number of threads (helper threads created due to the maximum number of handles), and then call the underlying select function of the operating system, The work of checking whether the channel is ready is handed over to the operating system. If it is Windows, it is detected by polling. If it is Linux, it may be select/poll or polling or a more efficient interrupt-based epoll method. It depends on the jdk version and the kernel version. When the underlying select call of the system returns, the key set that has been cancelled is cleaned up again, and the number of newly ready channels following the last select operation to the end of this time is returned.

 

4.Selector.wakeup(), by writing a byte to the sink end of the pipe (Windows is an interconnected socket channel) to wake up the call blocked in select, and multiple consecutive wakeup actions will be equivalent to one call.

 

2. Relevant knowledge points

1. First of all, Selector.open() is not a singleton mode. Every time you call the static method, a new Selector instance is returned.

 

2.Selector can set whether to enable non-blocking mode by calling configureBlocking. It defaults to blocking mode.

 

3. Whether the server and the client maintain the same Selector, the answer is no, the server and the client each maintain a Selector object, and pay attention to the multi-threaded concurrency, although the Selector is thread-safe, but its internal The important members of the collection (registeredKeys, selectedKeys, canceledKeys) are not thread safe. If there are any problems with multiple threads concurrently accessing a selector's set of keys, there are steps you can take to properly synchronize access.

 

4. When performing a select operation, the selector is synchronized on the Selector object, then the set of registered keys, and finally the set of selected keys, in that order. In a multithreaded scenario, if you need to make changes to any one collection of keys, either directly or as a side effect of other operations, you need to first synchronize on the same object, in the same order. The locking process is very important. If competing threads do not request locks in the same order, there is a potential for deadlock. If you can ensure that no other thread accesses the selector at the same time, then synchronization is unnecessary. 

 

5. The channel.read() function will return -1, so when will it read -1? For the server side, when the client calls channel.close() to close the connection, the read number returned by the server side is -1, indicating that the end has been reached. Then you need to cancel the corresponding SelectionKey at this time, indicating that the selector no longer listens to the read event on this channel, and closes the channel.

 

6. Although a channel can be registered to multiple selectors, it can only be registered once for each selector.

 

7. When the channel is closed, all related keys will be automatically canceled; when the selector is closed, all channels registered to the selector will be canceled, and the related keys will be invalidated immediately.

 

8. Note that select()the operation return value is not the total number of channels that have been prepared, but the number select()of channels that have entered the ready state since the last call. Channels that were ready in a previous call and are still ready in this call are not counted, nor are those channels that were ready in a previous call but are no longer ready. These channels may still be in the set of selected keys, but will not be counted in the return value, which may be 0, which is why you need to continue when 0 is returned.

 

9. The close( ) method of the Selector class is synchronized in the same way as the select( ) method, so there is a possibility of blocking all the time. While the selection process is still in progress, all calls to close( ) are blocked until the selection process ends, or the thread performing the selection goes to sleep.

 

10. When a channel is closed, its associated keys are also canceled. This does not affect an in-progress select( ), but it means that keys that are still valid until you call select( ) may become invalid on return.

三、SelectionKey

selectionKey represents the registration relationship between the channel (channel) and the selector (selector), and the events that maintain the channel.

SelectionKey contains two sets (actually byte masks encoded in integer form):

1. The set of registered operations of interest is the set of interestOps .

2. The ready set of operations (ready set) is: readyOps set.

 

Selector maintains three collections:

1、已经注册的键集合 调用, keys() 
2、已经选择的键集合 调用, selectedKeys() 
3、已经取消的键集合 私有, cancelledKeys

 

这些集合之间的流转关系:

1. 当调用cancel操作的时候,只是把要取消的键加入到了cancelledKeys键集合中,需要等到下次调用select的时候进行才会生效。但是SelectionKey的isValid()会立即回复false。

2. 在操作系统返回就绪操作的通道的时候:

a)如果通道的selectionkey还没有在已经选择的键的集合(selectedKeys)中,那么键的readyOps集合将被清空,然后表示操作系统发现的当前通道已经准备好的操作的比特掩码将被设置。

b)否则,一旦通道的键被放入已经选择的键的集合中时,ready集合不会被清除,而是累积。这就是说,如果之前的状态是ready的操作,本次已经不是ready了,但是他的bit位依然表示是ready,不会被清除。 

从2可以看出我们为什么每次循环selectedKeys的时候都需要调用it.remove().

 

另外:一个channel中的数据没读完(或有数据而不处理),那么,这个channel一直处于就绪状态中,所以每次selector的selectedKeys()方法总能返回与这个channel关联的Selectionkey,然后就会不停地循环select(),除非读完channel中的数据,或者把这个SelectionKey给cancel掉。

 

二、中断select()的方法

select()方法会阻塞住,等待有channel就绪才返回。有时候,希望停止阻塞,中断select方法,让线程继续。

有三种方法。 
1, wakeup()这是一种优雅的方法,立即返回阻塞在select的线程。如果当前没有阻塞在select上,则本次wakeup调用将作用在下一次select操作上,也就是下一次select调用将立即返回无论是否有就绪发生。
2, close()选择器的close被调用,则所有在选择操作中阻塞的线程被唤醒,相关通道被注销,键也被取消。 
3, interrupt() 实际上interrupt并不会中断线程。而是设置线程中断标志。 
然后依然是调用wakeup()。这是因为 Selector 捕获了interruptedException,然后在异常处理中调用了 wakeup() 

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326992005&siteId=291194637