NIO блокировки принцип бодрствование

Селектор: Java NiO ключ неблокирующая И.О. достижения.

Блокировка и неблокирующий IO Io:
блокирующие средства Io Io потока-ориентированная версию, потребности сервера , чтобы установить для каждого запроса до JDK1.4 кучи потоков , ожидающих для запроса, и клиент посылает запрос на консультацию сервера , если имеется соответствующий поток если нет , то придется ждать или отклонить запрос, если таковые имеются, клиентский поток ожидает окончания запроса , прежде чем продолжить выполнение.
Когда одновременно емкость, в то время как задний конец услуги или обработки данных клиента будут генерировать большое количество медленной нити ожидает, что выше закупорки.


 

Является ли однопоточный или неблокирующая Io только небольшое количество нескольких потоков, каждый поток подключен к общему, когда в ожидании (нет события) нити ресурсы не может быть освобождены ручками других запросы от модели событийного , когда принимает / чтение после событий / записи такого уведомления (просыпается) основной поток распределение ресурсов для решения связанных с ними событиями. java.nio.channels.Selector является наблюдателем событий в этой модели, несколько событий SocketChannel зарегистрированные в селекторе, когда не происходит событие Selector блокируется, когда там принимают SocketChannel / чтения / записи и другие события будить Selector происходит.
 

 Селектор является использование однопоточных модели, в основном используется для описания модели управляемых событий, чтобы Оптимизировать нужен хорошую потоковую модель для использования, относительно хорошая основа NiO Нетти, апач Мины и так далее. Установить ссылку на эту потоковую модель тогда, здесь мы сосредоточились на блокировании Selector и принцип пробуждения.

 

Посмотрите на простой кусок кода, что использование Selector

 

Java-код   Коллекция Код
  1. Селектор = Selector.open ();  
  2.   
  3. ServerSocketChannel ССК = ServerSocketChannel.open ();  
  4. ssc.configureBlocking ( ложь);  
  5. . ssc.socket () связываются ( новый InetSocketAddress (порт));  
  6.   
  7. ssc.register (селектор, SelectionKey.OP_ACCEPT);  
  8.   
  9. в то время как ( истинно) {  
  10.   
  11.     // выбрать () блоки, ожидание события произошли поминки  
  12.     INT = выбранный selector.select ();  
  13.   
  14.     если (выбрано>  0) {  
  15.         Итератора <SelectionKey> selectedKeys = selector.selectedKeys () итератора ().  
  16.         в то время как (selectedKeys.hasNext ()) {  
  17.             Ключ SelectionKey = selectedKeys.next ();  
  18.             если ((key.readyOps () & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {  
  19.                 // описатель события принимают  
  20.             }  Иначе  , если ((key.readyOps () & SelectionKey.OP_READ) == SelectionKey.OP_READ) {  
  21.                 // чтение обработка событий  
  22.             }  Иначе  , если ((key.readyOps () & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {  
  23.                 // обработка событий записи  
  24.             }  
  25.             selectedKeys.remove ();  
  26.         }  
  27.     }  
  28. }  

Код несколько ключевых моментов:
Selector.open ();
selector.select (),
после блокировки селектора бодрствования может зарегистрироваться на событии сокета происходит или selector.select (таймаут) тайм - аут или selector.wakeup () инициатива будить,

точка всего процесса включает в себя блокирующие и просыпаться очень много, на первый дразнить из общего вида, а затем в исходный код легче понять

 

 

 Теперь, чтобы решить все аспекты изображения выше, OpenJDK в исходном коде:

1. Selector.open ()

 

Java-код   Коллекция Код
  1. Selector.java  
  2. -----  
  3. общественности  статической Selector открытым ()  бросает IOException {    
  4.     Возвращение SelectorProvider.provider () openSelector ().    
  5. }  

 Посмотрите на SelectorProvider.provider () делает:

 

 

Java-код   Коллекция Код
  1. SelectorProvider.java  
  2. -----    
  3. общественный  статический провайдер SelectorProvider () {  
  4. синхронизированы (замок) {  
  5.     если (поставщик! =  NULL)  
  6.     вернуть поставщику;  
  7.     Возвращение (SelectorProvider) AccessController  
  8.     .doPrivileged ( новый PrivilegedAction () {  
  9.         public Object run() {  
  10.             if (loadProviderFromProperty())  
  11.             return provider;  
  12.             if (loadProviderAsService())  
  13.             return provider;  
  14.             provider = sun.nio.ch.DefaultSelectorProvider.create();  
  15.             return provider;  
  16.         }  
  17.         });  
  18. }  
  19. }  

 其中provider = sun.nio.ch.DefaultSelectorProvider.create();会根据操作系统来返回不同的实现类,windows平台就返回WindowsSelectorProvider;
这里主要以windows的实现来梳理整个流程,拿到provider后来看openSelector()中的实现

 

 

Java代码   Коллекция Код
  1. WindowsSelectorProvider.java  
  2. ----  
  3. public AbstractSelector openSelector() throws IOException {  
  4.     return new WindowsSelectorImpl(this);  
  5. }  
  6.   
  7. WindowsSelectorImpl.java  
  8. ----  
  9. WindowsSelectorImpl(SelectorProvider sp) throws IOException {  
  10.     super(sp);  
  11.     pollWrapper = new PollArrayWrapper(INIT_CAP);  
  12.     wakeupPipe = Pipe.open();  
  13.     wakeupSourceFd = ((SelChImpl)wakeupPipe.source()).getFDVal();  
  14.   
  15.     // Disable the Nagle algorithm so that the wakeup is more immediate  
  16.     SinkChannelImpl sink = (SinkChannelImpl)wakeupPipe.sink();  
  17.     (sink.sc).socket().setTcpNoDelay(true);  
  18.     wakeupSinkFd = ((SelChImpl)sink).getFDVal();  
  19.   
  20.     pollWrapper.addWakeupSocket(wakeupSourceFd, 0);  
  21. }  

 这段代码中做了如下几个事情
Pipe.open()打开一个管道(打开管道的实现后面再看);拿到wakeupSourceFd和wakeupSinkFd两个文件描述符;把唤醒端的文件描述符(wakeupSourceFd)放到pollWrapper里;
那么为什么需要一个管道,这个管道是怎么实现的?接下来看Pipe.open()做了什么

 

 

Java代码   Коллекция Код
  1. Pipe.java  
  2. ----  
  3. public static Pipe open() throws IOException {  
  4. return SelectorProvider.provider().openPipe();  
  5. }  

 同样,SelectorProvider.provider()也是获取操作系统相关的实现

 

 

Java代码   Коллекция Код
  1. SelectorProvider.java  
  2. ----  
  3. public Pipe openPipe() throws IOException {  
  4.     return new PipeImpl(this);  
  5. }  

 这里还是看windows下的实现

 

 

Java代码   Коллекция Код
  1. PipeImpl.java  
  2. ----  
  3. PipeImpl(final SelectorProvider sp) throws IOException {  
  4.     try {  
  5.         AccessController.doPrivileged(new Initializer(sp));  
  6.     } catch (PrivilegedActionException x) {  
  7.         throw (IOException)x.getCause();  
  8.     }  
  9. }  

 创建了一个PipeImpl对象, AccessController.doPrivileged调用后紧接着会执行initializer的run方法

 

 

Java代码   Коллекция Код
  1. PipeImpl.Initializer  
  2. -----  
  3. public Object run() throws IOException {  
  4.     ServerSocketChannel ssc = null;  
  5.     SocketChannel sc1 = null;  
  6.     SocketChannel sc2 = null;  
  7.   
  8.     try {  
  9.         // loopback address  
  10.         InetAddress lb = InetAddress.getByName("127.0.0.1");  
  11.         assert(lb.isLoopbackAddress());  
  12.   
  13.         // bind ServerSocketChannel to a port on the loopback address  
  14.         ssc = ServerSocketChannel.open();  
  15.         ssc.socket().bind(new InetSocketAddress(lb, 0));  
  16.   
  17.         // Establish connection (assumes connections are eagerly  
  18.         // accepted)  
  19.         InetSocketAddress sa  
  20.             = new InetSocketAddress(lb, ssc.socket().getLocalPort());  
  21.         sc1 = SocketChannel.open(sa);  
  22.   
  23.         ByteBuffer bb = ByteBuffer.allocate(8);  
  24.         long secret = rnd.nextLong();  
  25.         bb.putLong(secret).flip();  
  26.         sc1.write(bb);  
  27.   
  28.         // Get a connection and verify it is legitimate  
  29.         for (;;) {  
  30.             sc2 = ssc.accept();  
  31.             bb.clear();  
  32.             sc2.read(bb);  
  33.             bb.rewind();  
  34.             if (bb.getLong() == secret)  
  35.                 break;  
  36.             sc2.close();  
  37.         }  
  38.   
  39.         // Create source and sink channels  
  40.         source = new SourceChannelImpl(sp, sc1);  
  41.         sink = new SinkChannelImpl(sp, sc2);  
  42.     } catch (IOException e) {  
  43.         try {  
  44.             if (sc1 != null)  
  45.                 sc1.close();  
  46.             if (sc2 != null)  
  47.                 sc2.close();  
  48.         } catch (IOException e2) { }  
  49.         IOException x = new IOException("Unable to establish"  
  50.                                         + " loopback connection");  
  51.         x.initCause(e);  
  52.         throw x;  
  53.     } finally {  
  54.         try {  
  55.             if (ssc != null)  
  56.                 ssc.close();  
  57.         } catch (IOException e2) { }  
  58.     }  
  59.     return null;  
  60. }  

 这里即为上图中最下面那部分创建pipe的过程,windows下的实现是创建两个本地的socketChannel,然后连接(链接的过程通过写一个随机long做两个socket的链接校验),两个socketChannel分别实现了管道的source与sink端。
source端由前面提到的WindowsSelectorImpl放到了pollWrapper中(pollWrapper.addWakeupSocket(wakeupSourceFd, 0))

 

 

Java代码   Коллекция Код
  1. PollArrayWrapper.java  
  2. ----  
  3. private AllocatedNativeObject pollArray; // The fd array  
  4.   
  5. // Adds Windows wakeup socket at a given index.  
  6. void addWakeupSocket(int fdVal, int index) {  
  7.     putDescriptor(index, fdVal);  
  8.     putEventOps(index, POLLIN);  
  9. }  
  10.   
  11. // Access methods for fd structures  
  12. void putDescriptor(int i, int fd) {  
  13.     pollArray.putInt(SIZE_POLLFD * i + FD_OFFSET, fd);  
  14. }  
  15.   
  16. void putEventOps(int i, int event) {  
  17.     pollArray.putShort(SIZE_POLLFD * i + EVENT_OFFSET, (short)event);  
  18. }  

 这里将source的POLLIN事件标识为感兴趣的,当sink端有数据写入时,source对应的文件描述符wakeupSourceFd就会处于就绪状态

 

 

Java代码   Коллекция Код
  1. AllocatedNativeObject.java  
  2. ----  
  3. class AllocatedNativeObject extends NativeObject  
  4.   
  5. AllocatedNativeObject(int size, boolean pageAligned) {  
  6.     super(size, pageAligned);  
  7. }  
  8.   
  9. NativeObject.java  
  10. ----  
  11. protected NativeObject(int size, boolean pageAligned) {  
  12.     if (!pageAligned) {  
  13.         this.allocationAddress = unsafe.allocateMemory(size);  
  14.         this.address = this.allocationAddress;  
  15.     } else {  
  16.         int ps = pageSize();  
  17.         long a = unsafe.allocateMemory(size + ps);  
  18.         this.allocationAddress = a;  
  19.         this.address = a + ps - (a & (ps - 1));  
  20.     }  
  21. }  

 从以上可以看到pollArray是通过unsafe.allocateMemory(size + ps)分配的一块系统内存

到这里完成了Selector.open(),主要完成建立Pipe,并把pipe的wakeupSourceFd放入pollArray中,这个pollArray是Selector的枢纽。这里是以Windows的实现来看,在windows下通过两个链接的socketChannel实现了Pipe,linux下则是直接使用系统的pipe。

 

2. serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

 

Java代码   Коллекция Код
  1. AbstractSelectableChannel.java --> register() --> SelectorImpl.java  
  2. ----  
  3. protected final SelectionKey register(AbstractSelectableChannel ch,int ops,Object attachment)  
  4. {  
  5.     if (!(ch instanceof SelChImpl))  
  6.         throw new IllegalSelectorException();  
  7.     SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);  
  8.     k.attach(attachment);  
  9.     synchronized (publicKeys) {  
  10.         implRegister(k);  
  11.     }  
  12.     k.interestOps(ops);  
  13.     return k;  
  14. }  

 关键是implRegister(k);

 

 

Java代码   Коллекция Код
  1. WindowsSelectorImpl.java  
  2. ----  
  3. protected void implRegister(SelectionKeyImpl ski) {  
  4.     growIfNeeded();  
  5.     channelArray[totalChannels] = ski;  
  6.     ski.setIndex(totalChannels);  
  7.     fdMap.put(ski);  
  8.     keys.add(ski);  
  9.     pollWrapper.addEntry(totalChannels, ski);  
  10.     totalChannels++;  
  11. }  
  12.   
  13. PollArrayWrapper.java  
  14. ----  
  15. void addEntry(int index, SelectionKeyImpl ski) {  
  16.     putDescriptor(index, ski.channel.getFDVal());  
  17. }  

 这里把socketChannel的文件描述符放到pollArray中。

 

 

3. selector.select();

 

Java代码   Коллекция Код
  1. SelectorImpl.java  
  2. ----  
  3. public int select(long timeout) throws IOException  
  4. {  
  5.     if (timeout < 0)  
  6.         throw new IllegalArgumentException("Negative timeout");  
  7.     return lockAndDoSelect((timeout == 0) ? -1 : timeout);  
  8. }  
  9.   
  10. private int lockAndDoSelect(long timeout) throws IOException {  
  11.     synchronized (this) {  
  12.         if (!isOpen())  
  13.             throw new ClosedSelectorException();  
  14.         synchronized (publicKeys) {  
  15.             synchronized (publicSelectedKeys) {  
  16.                 return doSelect(timeout);  
  17.             }  
  18.         }  
  19.     }  
  20. }  

 其中的doSelector又回到我们的Windows实现:

 

 

Java代码   Коллекция Код
  1. WindowsSelectorImpl.java  
  2. ----  
  3. protected int doSelect(long timeout) throws IOException {  
  4.     if (channelArray == null)  
  5.         throw new ClosedSelectorException();  
  6.     this.timeout = timeout; // set selector timeout  
  7.     processDeregisterQueue();  
  8.     if (interruptTriggered) {  
  9.         resetWakeupSocket();  
  10.         return 0;  
  11.     }  
  12.     // Calculate number of helper threads needed for poll. If necessary  
  13.     // threads are created here and start waiting on startLock  
  14.     adjustThreadsCount();  
  15.     finishLock.reset(); // reset finishLock  
  16.     // Wakeup helper threads, waiting on startLock, so they start polling.  
  17.     // Redundant threads will exit here after wakeup.  
  18.     startLock.startThreads();  
  19.     // do polling in the main thread. Main thread is responsible for  
  20.     // first MAX_SELECTABLE_FDS entries in pollArray.  
  21.     try {  
  22.         begin();  
  23.         try {  
  24.             subSelector.poll();  
  25.         } catch (IOException e) {  
  26.             finishLock.setException(e); // Save this exception  
  27.         }  
  28.         // Main thread is out of poll(). Wakeup others and wait for them  
  29.         if (threads.size() > 0)  
  30.             finishLock.waitForHelperThreads();  
  31.       } finally {  
  32.           end();  
  33.       }  
  34.     // Done with poll(). Set wakeupSocket to nonsignaled  for the next run.  
  35.     finishLock.checkForException();  
  36.     processDeregisterQueue();  
  37.     int updated = updateSelectedKeys();  
  38.     // Done with poll(). Set wakeupSocket to nonsignaled  for the next run.  
  39.     resetWakeupSocket();  
  40.     return updated;  
  41. }  
  42.   
  43. private int poll() throws IOException{ // poll for the main thread  
  44.     return poll0(pollWrapper.pollArrayAddress,  
  45.                  Math.min(totalChannels, MAX_SELECTABLE_FDS),  
  46.                  readFds, writeFds, exceptFds, timeout);  
  47. }  

 private native int poll0(long pollAddress, int numfds, int[] readFds, int[] writeFds, int[] exceptFds, long timeout);
其他的都是一些准备工作,关键是subSelector.poll(),最后调用了native的poll0,并把pollWrapper.pollArrayAddress作为参数传给poll0,那么poll0对pollArray做了什么:

 

 

Java代码   Коллекция Код
  1. WindowsSelectorImpl.c  
  2. ----  
  3. Java_sun_nio_ch_WindowsSelectorImpl_00024SubSelector_poll0(JNIEnv *env, jobject this,  
  4.                                    jlong pollAddress, jint numfds,  
  5.                                    jintArray returnReadFds, jintArray returnWriteFds,  
  6.                                    jintArray returnExceptFds, jlong timeout)  
  7. {  
  8.                                      
  9.     // 代码.... 此处省略一万字  
  10.   
  11.     /* Call select */  
  12.     if ((result = select(0 , &readfds, &writefds, &exceptfds, tv)) == SOCKET_ERROR) {  
  13.       
  14.         // 代码.... 此处省略一万字  
  15.           
  16.         for (i = 0; i < numfds; i++) {  
  17.             // 代码.... 此处省略一万字  
  18.         }                                                      
  19.     }  
  20. }  

 C代码已经忘得差不多了,但这里可以看到实现思路是调用c的select方法,这里的select对应于内核中的sys_select调用,sys_select首先将第二三四个参数指向的fd_set拷贝到内核,然后对每个被SET的描述符调用进行poll,并记录在临时结果中(fdset),如果有事件发生,select会将临时结果写到用户空间并返回;当轮询一遍后没有任何事件发生时,如果指定了超时时间,则select会睡眠到超时,睡眠结束后再进行一次轮询,并将临时结果写到用户空间,然后返回。
这里的select就是轮询pollArray中的FD,看有没有事件发生,如果有事件发生收集所有发生事件的FD,退出阻塞。
关于select系统调用参考了《select、poll、epoll的比较》这篇文章,同时看到nio的select在不同平台上的实现不同,在linux上通过epoll可以不用轮询,在第一次调用后,事件信息就会与对应的epoll描述符关联起来,待的描述符上注册回调函数,当事件发生时,回调函数负责把发生的事件存储在就绪事件链表中,最后写到用户空间。

到这里已经比较清楚了,退出阻塞的方式有:regist在selector上的socketChannel处于就绪状态(放在pollArray中的socketChannel的FD就绪) 或者 第1节中放在pollArray中的wakeupSourceFd就绪。前者(socketChannel)就绪唤醒应证了文章开始的阻塞->事件驱动->唤醒的过程,后者(wakeupSourceFd)就是下面要看的主动wakeup。

 

 

4. selector.wakeup()

 

Java代码   Коллекция Код
  1. WindowsSelectorImpl.java  
  2. ----  
  3. public Selector wakeup() {  
  4.     synchronized (interruptLock) {  
  5.         if (!interruptTriggered) {  
  6.             setWakeupSocket();  
  7.             interruptTriggered = true;  
  8.         }  
  9.     }  
  10.     return this;  
  11. }  
  12.   
  13. // Sets Windows wakeup socket to a signaled state.  
  14. private void setWakeupSocket() {  
  15.     setWakeupSocket0(wakeupSinkFd);  
  16. }  
  17.   
  18. private native void setWakeupSocket0(int wakeupSinkFd);  

 native实现摘要:

 

Java代码   Коллекция Код
  1. WindowsSelectorImpl.c  
  2. ----  
  3. Java_sun_nio_ch_WindowsSelectorImpl_setWakeupSocket0(JNIEnv *env, jclass this,  
  4.                                                 jint scoutFd)  
  5. {  
  6.     /* Write one byte into the pipe */  
  7.     send(scoutFd, (char*)&POLLIN, 1, 0);  
  8. }  

 Вот раковина завершена конец трубы до начала установления письменного байта, исходный дескриптор файла будет находиться в состоянии готовности, опрос метод возвращает, в результате чего выбери метод возвращает. (Я понимаю , что я строй самого сокета цепи к другой розетке , чтобы сделать работу)

для этого с помощью анализа исходного кода , чтобы разобраться в Selector блокирующего принцип бодрствования

Печатается: https://blog.csdn.net/god8816/article/details/54320053

рекомендация

отwww.cnblogs.com/minikobe/p/12167584.html