JAVA NIO 选择器

原创 2013年06月19日 11:39:46

为什么要使用选择器

通道处于就绪状态后,就可以在缓冲区之间传送数据。可以采用非阻塞模式来检查通道是否就绪,但非阻塞模式还会做别的任务,当有多个通道同时存在时,很难将检查通道是否就绪与其他任务剥离开来,或者说是这样做很复杂,即使完成了这样的功能,但每检查一次通道的就绪状态,就至少有一次系统调用,代价十分昂贵。当你轮询每个通道的就绪状态时,刚被检查的一个处于未就绪状态的通道,突然处于就绪状态,在下一次轮询之前是不会被察觉的。操作系统拥有这种检查就绪状态并通知就绪的能力,因此要充分利用操作系统提供的服务。在JAVA中,Selector类提供了这种抽象,拥有询问通道是否已经准备好执行每个I/0操作的能力,所以可以利用选择器来很好地解决以上问题。

如何使用选择器

使用选择器时,需要将一个或多个可选择的通道注册到选择器对象中,注册后会返回一个选择键,选择器会记住这些通道以及这些通道感兴趣的操作,还会追踪对应的通道是否已经就绪。调用选择器对象的select( )方法,当有通道就绪时,相关的键会被更新。可以获取选择键的集合,从而找到已经就绪的通道。

这里提到的选择器、选择键与可选择通道之间的关系如下图所示


先看一段使用选择器的代码

ServerSocketChannel serverChannel = ServerSocketChannel.open;
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(1234));
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
	selector.select();
	Iterator<SelectionKey> itor = selector.selectedKeys().iterator();
	while (itor.hasNext()) {
		SelectionKey key = itor.next();
		itor.remove();
		if (key.isAcceptable()) {
			ServerSocketChannel server = (ServerSocketChannel) key.channel();
			SocketChannel channel = server.accept();
			channel.configureBlocking(false);
			channel.write(ByteBuffer.wrap("hello".getBytes()));
			channel.register(selector, SelectionKey.OP_READ);
		} else if (key.isReadable()) {
			//read();
		}
	}
}
以上代码向选择器(selector)注册了两个可选择通道serverChannel和channel,其中serverChannel对accept感兴趣,channel对read感兴趣。当select()方法返回后,轮询选择键,找到准备就绪的通道,在这里是serverChannel的accept处于就绪状态,证明有连接到来,于是接受连接,得到一个通道并向这个通道写入"hello",同时对这个通道的read感兴趣,所以也将其注册到选择器上,当连接的另一端有数据到来时,key.isReadable()返回true,可以读取数据。

打开通道

从Selector源码中可以看到,open方法是交给selectorProvider处理的

public static Selector open() throws IOException {
	return SelectorProvider.provider().openSelector();
    }
selectorProvider不同的操作系统有不同的实现,这里以windows为例
//WindowsSelectorProvider.java
public AbstractSelector openSelector() throws IOException {
	return new WindowsSelectorImpl(this);
}

//WindowsSelectorImpl.java
WindowsSelectorImpl(SelectorProvider sp) throws IOException {
    super(sp);
    pollWrapper = new PollArrayWrapper(INIT_CAP);
    wakeupPipe = Pipe.open();
    wakeupSourceFd = ((SelChImpl)wakeupPipe.source()).getFDVal();

    // Disable the Nagle algorithm so that the wakeup is more immediate
    SinkChannelImpl sink = (SinkChannelImpl)wakeupPipe.sink();
    (sink.sc).socket().setTcpNoDelay(true);
    wakeupSinkFd = ((SelChImpl)sink).getFDVal();

    pollWrapper.addWakeupSocket(wakeupSourceFd, 0);
}

//PollArrayWrapper.java
void addWakeupSocket(int fdVal, int index) {
	putDescriptor(index, fdVal);
	putEventOps(index, POLLIN);
}
这里创建了一个管道pipe,并对pipe的source端的POLLIN事件感兴趣,addWakeupSocket方法将source的POLLIN事件标识为感兴趣的,当sink端有数据写入时,source对应的文件描述描wakeupSourceFd就会处于就绪状态。(事实上windows就是通过向管道中写数据来唤醒阻塞的选择器的)从以上代码可以看出:通道的打开实际上是构造了一个SelectorImpl对象。

注册通道

有了选择器后就可以注册通道了,注册通道的方法定义在SelectableChannel类中
public final SelectionKey register(Selector sel,int ops)
                            throws ClosedChannelException
第二个参数ops指示选择键的interest集,可以是OP_READ、OP_WRITE、OP_ACCEPT等,分别表示对读、写、连接到来感兴趣。
注册通道的核心方法是implRegister,仍然以windows为例
protected void implRegister(SelectionKeyImpl ski) {
    synchronized (closeLock) {
        if (pollWrapper == null)
            throw new ClosedSelectorException();
        growIfNeeded();
        channelArray[totalChannels] = ski;
        ski.setIndex(totalChannels);
        fdMap.put(ski);
        keys.add(ski);
        pollWrapper.addEntry(totalChannels, ski);
        totalChannels++;
    }
}
先看看growIfNeeded方法
private void growIfNeeded() {
      if (channelArray.length == totalChannels) {
          int newSize = totalChannels * 2; // Make a larger array
          SelectionKeyImpl temp[] = new SelectionKeyImpl[newSize];
          System.arraycopy(channelArray, 1, temp, 1, totalChannels - 1);
          channelArray = temp;
          pollWrapper.grow(newSize);
      }
      if (totalChannels % MAX_SELECTABLE_FDS == 0) { // more threads needed
          pollWrapper.addWakeupSocket(wakeupSourceFd, totalChannels);
          totalChannels++;
          threadsCount++;
      }
  }
做了两件事:
1、调整channelArray数组大小;
2、增加进行select的线程数,每达到MAX_SELECTABLE_FDS=1024个描述符,就增加一个线程,windows上select系统调用有最大文件描述符限制,一次只能轮询1024个文件描述符,如果多于1024个,需要多线程进行轮询。
implRegister方法设置选择键在数组中的位置,并将其加入已注册的键的集合(keys)中,fdMap是文件描述符到选择键的映射。

选择过程

有三种形式的select方法:select()、select(timeout)、selectNow(),最终都调用了doSelect()方法
protected int doSelect(long timeout) throws IOException {
  if (channelArray == null)
      throw new ClosedSelectorException();
  this.timeout = timeout; // set selector timeout
  processDeregisterQueue();
  if (interruptTriggered) {
      resetWakeupSocket();
      return 0;
  }
  // Calculate number of helper threads needed for poll. If necessary
  // threads are created here and start waiting on startLock
  adjustThreadsCount();
  finishLock.reset(); // reset finishLock
  // Wakeup helper threads, waiting on startLock, so they start polling.
  // Redundant threads will exit here after wakeup.
  startLock.startThreads();
  // do polling in the main thread. Main thread is responsible for
  // first MAX_SELECTABLE_FDS entries in pollArray.
  try {
      begin();
      try {
          subSelector.poll();
      } catch (IOException e) {
          finishLock.setException(e); // Save this exception
      }
      // Main thread is out of poll(). Wakeup others and wait for them
      if (threads.size() > 0)
          finishLock.waitForHelperThreads();
    } finally {
        end();
    }
  // Done with poll(). Set wakeupSocket to nonsignaled  for the next run.
  finishLock.checkForException();
  processDeregisterQueue();
  int updated = updateSelectedKeys();
  // Done with poll(). Set wakeupSocket to nonsignaled  for the next run.
  resetWakeupSocket();
  return updated;
}
processDeregisterQueue方法主要是对已取消的键集合进行处理,通过调用cancel()方法将选择键加入已取消的键集合中,这个键并不会立即注销,而是在下一次select操作时进行注销,注销操作在implDereg完成
protected void implDereg(SelectionKeyImpl ski) throws IOException{
    int i = ski.getIndex();
    assert (i >= 0);
    if (i != totalChannels - 1) {
        // Copy end one over it
        SelectionKeyImpl endChannel = channelArray[totalChannels-1];
        channelArray[i] = endChannel;
        endChannel.setIndex(i);
        pollWrapper.replaceEntry(pollWrapper, totalChannels - 1,
                                                            pollWrapper, i);
    }
    channelArray[totalChannels - 1] = null;
    totalChannels--;
    ski.setIndex(-1);
    if ( totalChannels != 1 && totalChannels % MAX_SELECTABLE_FDS == 1) {
        totalChannels--;
        threadsCount--; // The last thread has become redundant.
    }
    fdMap.remove(ski); // Remove the key from fdMap, keys and selectedKeys
    keys.remove(ski);
    selectedKeys.remove(ski);
    deregister(ski);
    SelectableChannel selch = ski.channel();
    if (!selch.isOpen() && !selch.isRegistered())
        ((SelChImpl)selch).kill();
}

从channelArray中移除对应的通道,调整通道数和线程数,从map和keys中移除选择键,移除通道上的选择键并关闭通道

adjustThreadsCount这个方法前面提到过,与windows下select有文件描述符限制有关,需要多线程select

    private void adjustThreadsCount() {
        if (threadsCount > threads.size()) {
            // More threads needed. Start more threads.
            for (int i = threads.size(); i < threadsCount; i++) {
                SelectThread newThread = new SelectThread(i);
                threads.add(newThread);
                newThread.setDaemon(true);
                newThread.start();
            }
        } else if (threadsCount < threads.size()) {
            // Some threads become redundant. Remove them from the threads List.
            for (int i = threads.size() - 1 ; i >= threadsCount; i--)
                threads.remove(i).makeZombie();
        }
    }
当前线程如果比threadsCount小就新建,如果比threadsCount大就移除,比较容易理解,来看看线程的run方法

public void run() {
    while (true) { // poll loop
        // wait for the start of poll. If this thread has become
        // redundant, then exit.
        if (startLock.waitForStart(this))
            return;
        // call poll()
        try {
            subSelector.poll(index);
        } catch (IOException e) {
            // Save this exception and let other threads finish.
            finishLock.setException(e);
        }
        // notify main thread, that this thread has finished, and
        // wakeup others, if this thread is the first to finish.
        finishLock.threadFinished();
    }
}

private synchronized boolean waitForStart(SelectThread thread) {
  while (true) {
      while (runsCounter == thread.lastRun) {
          try {
              startLock.wait();
          } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
          }
      }
      if (thread.isZombie()) { // redundant thread
          return true; // will cause run() to exit.
      } else {
          thread.lastRun = runsCounter; // update lastRun
          return false; //   will cause run() to poll.
      }
  }
}

可以看到这些helper线程创建好后,都阻塞在startLock.wait()上面,待主线程(doSelect方法)调用startLock.startThreads()后,waitForStart方法将返回false

// Triggers threads, waiting on this lock to start polling.
private synchronized void startThreads() {
    runsCounter++; // next run
    notifyAll(); // wake up threads.
}
紧接着调用subSelector.poll(index)轮询各个文件描述符,同时主线程也在进行轮询,意思是所有线程(主线程和helper线程)都在轮询文件描述符。

如果在这期间,有文件描述符准备就绪,poll方法就会返回,不管是主线程返回还是helper线程返回,其他线程都会被唤醒

private synchronized void waitForHelperThreads() {
    if (threadsToFinish == threads.size()) {
        // no helper threads finished yet. Wakeup them up.
        wakeup();
    }
    while (threadsToFinish != 0) {
        try {
            finishLock.wait();
        } catch (InterruptedException e) {
            // Interrupted - set interrupted state.
            Thread.currentThread().interrupt();
        }
    }
}
如果是主线程poll返回,会调用waitForHelperThreads方法唤醒helper线程,如果是其中一个helper线程返回,会调用threadFinished方法唤醒其他helper线程和主线程

 private synchronized void threadFinished() {
            if (threadsToFinish == threads.size()) { // finished poll() first
                // if finished first, wakeup others
                wakeup();
            }
            threadsToFinish--;
            if (threadsToFinish == 0) // all helper threads finished poll().
                notify();             // notify the main thread
        }
因为整个轮询的过程中可能有其他键注册失败,或者调用了cancel方法,这里再次调用processDeregisterQueue()方法清理一下

现在把注意力放到updateSelectedKeys方法上,这个方法完成了选择键的更新,来看具体实现

    private int updateSelectedKeys() {
        updateCount++;
        int numKeysUpdated = 0;
        numKeysUpdated += subSelector.processSelectedKeys(updateCount);
        for (SelectThread t: threads) {
            numKeysUpdated += t.subSelector.processSelectedKeys(updateCount);
        }
        return numKeysUpdated;
    }
对主线程和各个helper线程都调用了processSelectedKeys方法

private int processSelectedKeys(long updateCount) {
    int numKeysUpdated = 0;
    numKeysUpdated += processFDSet(updateCount, readFds,
                                   PollArrayWrapper.POLLIN,
                                   false);
    numKeysUpdated += processFDSet(updateCount, writeFds,
                                   PollArrayWrapper.POLLCONN |
                                   PollArrayWrapper.POLLOUT,
                                   false);
    numKeysUpdated += processFDSet(updateCount, exceptFds,
                                   PollArrayWrapper.POLLIN |
                                   PollArrayWrapper.POLLCONN |
                                   PollArrayWrapper.POLLOUT,
                                   true);
    return numKeysUpdated;
}
processSelectedKeys方法分别对读选择键集、写选择键集,异常选择键集调用了processFDSet方法

private int processFDSet(long updateCount, int[] fds, int rOps,
                                 boolean isExceptFds){
          int numKeysUpdated = 0;
          for (int i = 1; i <= fds[0]; i++) {
              int desc = fds[i];
              if (desc == wakeupSourceFd) {
                  synchronized (interruptLock) {
                      interruptTriggered = true;
                  }
                  continue;
              }
              MapEntry me = fdMap.get(desc);
              // If me is null, the key was deregistered in the previous
              // processDeregisterQueue.
              if (me == null)
                  continue;
              SelectionKeyImpl sk = me.ski;

              // The descriptor may be in the exceptfds set because there is
              // OOB data queued to the socket. If there is OOB data then it
              // is discarded and the key is not added to the selected set.
              if (isExceptFds &&
                  (sk.channel() instanceof SocketChannelImpl) &&
                  discardUrgentData(desc))
              {
                  continue;
              }

              if (selectedKeys.contains(sk)) { // Key in selected set
                  if (me.clearedCount != updateCount) {
                      if (sk.channel.translateAndSetReadyOps(rOps, sk) &&
                          (me.updateCount != updateCount)) {
                          me.updateCount = updateCount;
                          numKeysUpdated++;
                      }
                  } else { // The readyOps have been set; now add
                      if (sk.channel.translateAndUpdateReadyOps(rOps, sk) &&
                          (me.updateCount != updateCount)) {
                          me.updateCount = updateCount;
                          numKeysUpdated++;
                      }
                  }
                  me.clearedCount = updateCount;
              } else { // Key is not in selected set yet
                  if (me.clearedCount != updateCount) {
                      sk.channel.translateAndSetReadyOps(rOps, sk);
                      if ((sk.nioReadyOps() & sk.nioInterestOps()) != 0) {
                          selectedKeys.add(sk);
                          me.updateCount = updateCount;
                          numKeysUpdated++;
                      }
                  } else { // The readyOps have been set; now add
                      sk.channel.translateAndUpdateReadyOps(rOps, sk);
                      if ((sk.nioReadyOps() & sk.nioInterestOps()) != 0) {
                          selectedKeys.add(sk);
                          me.updateCount = updateCount;
                          numKeysUpdated++;
                      }
                  }
                  me.clearedCount = updateCount;
              }
          }
          return numKeysUpdated;
      }
  }
以上方法完成了更新选择键,步骤如下:

1、忽略wakeupSourceFd,这个文件描述符用于唤醒用的,与用户具体操作无关,所以忽略;

2、过滤fdMap中不存在的文件描述符,因为已被注销;

3、忽略oob data(搜了一下:out of band data指带外数据,有时也称为加速数据, 是指连接双方中的一方发生重要事情,想要迅速地通知对方 ),这也不是用户关心的;

4、如果通道的键还没有处于已选择的键的集合中,那么键的ready集合将被清空,然后表示操作系统发现的当前通道已经准备好的操作的比特掩码将被设置;

5、如果键在已选择的键的集合中。键的ready集合将被表示操作系统发现的当前已经准备好的操作的比特掩码更新。

来看下具体的更新ready集的方法translateAndUpdateReadyOps,不同的通道有不同的实现,以socketChannel为例

    public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl sk) {
        return translateReadyOps(ops, sk.nioReadyOps(), sk);
    }
    public boolean translateReadyOps(int ops, int initialOps,
                                     SelectionKeyImpl sk) {
        int intOps = sk.nioInterestOps(); // Do this just once, it synchronizes
        int oldOps = sk.nioReadyOps();
        int newOps = initialOps;

        if ((ops & PollArrayWrapper.POLLNVAL) != 0) {
            // This should only happen if this channel is pre-closed while a
            // selection operation is in progress
            // ## Throw an error if this channel has not been pre-closed
            return false;
        }

        if ((ops & (PollArrayWrapper.POLLERR
                    | PollArrayWrapper.POLLHUP)) != 0) {
            newOps = intOps;
            sk.nioReadyOps(newOps);
            // No need to poll again in checkConnect,
            // the error will be detected there
            readyToConnect = true;
            return (newOps & ~oldOps) != 0;
        }

        if (((ops & PollArrayWrapper.POLLIN) != 0) &&
            ((intOps & SelectionKey.OP_READ) != 0) &&
            (state == ST_CONNECTED))
            newOps |= SelectionKey.OP_READ;

        if (((ops & PollArrayWrapper.POLLCONN) != 0) &&
            ((intOps & SelectionKey.OP_CONNECT) != 0) &&
            ((state == ST_UNCONNECTED) || (state == ST_PENDING))) {
            newOps |= SelectionKey.OP_CONNECT;
            readyToConnect = true;
        }

        if (((ops & PollArrayWrapper.POLLOUT) != 0) &&
            ((intOps & SelectionKey.OP_WRITE) != 0) &&
            (state == ST_CONNECTED))
            newOps |= SelectionKey.OP_WRITE;

        sk.nioReadyOps(newOps);
        return (newOps & ~oldOps) != 0;
    }
总之,最终是通过调用sk.nioReadyOps(newOps)来设置新的ready集的。

把目光转向selectkey类的几个方法

public final boolean isAcceptable() {
	return (readyOps() & OP_ACCEPT) != 0;
}
public final boolean isConnectable() {
	return (readyOps() & OP_CONNECT) != 0;
}
public final boolean isWritable() {
	return (readyOps() & OP_WRITE) != 0;
}
public final boolean isReadable() {
	return (readyOps() & OP_READ) != 0;
}
可以看出一个通道是否可读、可写、可连接就是通过对ready集进行与操作来判断的。

总结一下doSelect:处理已取消的键集,通过本地方法poll轮询文件描述符,poll方法返回后更新已选择键的ready集。

唤醒

如果线程正阻塞在select方法上,调用wakeup方法会使阻塞的选择操作立即返回

    public Selector wakeup() {
        synchronized (interruptLock) {
            if (!interruptTriggered) {
                setWakeupSocket();
                interruptTriggered = true;
            }
        }
        return this;
    }
//WindowsSelectorImpl.java
private void setWakeupSocket() {
    setWakeupSocket0(wakeupSinkFd);
}
private native void setWakeupSocket0(int wakeupSinkFd);
    

//WindowsSelectorImpl.c    
JNIEXPORT void JNICALL
Java_sun_nio_ch_WindowsSelectorImpl_setWakeupSocket0(JNIEnv *env, jclass this,
                                                jint scoutFd)
{
    /* Write one byte into the pipe */
    send(scoutFd, (char*)&POLLIN, 1, 0);
}
向pipe的sink端写入了一个字节,source文件描述符就会处于就绪状态,poll方法会返回,从而导致select方法返回。

这里有必要提一下打开通道pipe.open的实现细节,先看看windows的实现

public static Pipe open() throws IOException {
	return SelectorProvider.provider().openPipe();
}

public Pipe openPipe() throws IOException {
  return new PipeImpl(this);
}

PipeImpl(final SelectorProvider sp) throws IOException {
	try {
	   AccessController.doPrivileged(new Initializer(sp));
	} catch (PrivilegedActionException x) {
	   throw (IOException)x.getCause();
	}
}
创建了一个PipeImpl对象, AccessController.doPrivileged调用后紧接着会执行initializer的run方法

public Void run() throws IOException {
	ServerSocketChannel ssc = null;
	SocketChannel sc1 = null;
	SocketChannel sc2 = null;

	try {
		// loopback address
		InetAddress lb = InetAddress.getByName("127.0.0.1");
		assert (lb.isLoopbackAddress());

		// bind ServerSocketChannel to a port on the loopback address
		ssc = ServerSocketChannel.open();
		ssc.socket().bind(new InetSocketAddress(lb, 0));

		// Establish connection (assumes connections are eagerly
		// accepted)
		InetSocketAddress sa = new InetSocketAddress(lb, ssc.socket().getLocalPort());
		sc1 = SocketChannel.open(sa);

		ByteBuffer bb = ByteBuffer.allocate(8);
		long secret = rnd.nextLong();
		bb.putLong(secret).flip();
		sc1.write(bb);

		// Get a connection and verify it is legitimate
		for (;;) {
			sc2 = ssc.accept();
			bb.clear();
			sc2.read(bb);
			bb.rewind();
			if (bb.getLong() == secret)
				break;
			sc2.close();
		}

		// Create source and sink channels
		source = new SourceChannelImpl(sp, sc1);
		sink = new SinkChannelImpl(sp, sc2);
	} catch (IOException e) {
		
	}
}
该方法创建了两个通道sc1和sc2,这两个通道都绑定了本地ip,然后sc1向sc2写入了一个随机长整型的数,这两个通道分别做为管道的source与sink端。这相当于利用了回送地址(loopback address)自己向自己写数据,来达到通知的目的。
看看sun solaris的实现

PipeImpl(SelectorProvider sp) {
    int[] fdes = new int[2];
    IOUtil.initPipe(fdes, true);
    FileDescriptor sourcefd = new FileDescriptor();
    IOUtil.setfdVal(sourcefd, fdes[0]);
    source = new SourceChannelImpl(sp, sourcefd);
    FileDescriptor sinkfd = new FileDescriptor();
    IOUtil.setfdVal(sinkfd, fdes[1]);
    sink = new SinkChannelImpl(sp, sinkfd);
}


JNIEXPORT void JNICALL
Java_sun_nio_ch_IOUtil_initPipe(JNIEnv *env, jobject this,
                                    jintArray intArray, jboolean block)
{
    int fd[2];
    jint *ptr = 0;

    if (pipe(fd) < 0) {
        JNU_ThrowIOExceptionWithLastError(env, "Pipe failed");
        return;
    }
    if (block == JNI_FALSE) {
        if ((configureBlocking(fd[0], JNI_FALSE) < 0)
            || (configureBlocking(fd[1], JNI_FALSE) < 0)) {
            JNU_ThrowIOExceptionWithLastError(env, "Configure blocking failed");
        }
    }
    ptr = (*env)->GetPrimitiveArrayCritical(env, intArray, 0);
    ptr[0] = fd[0];
    ptr[1] = fd[1];
    (*env)->ReleasePrimitiveArrayCritical(env, intArray, ptr, 0);
}
可见solaris上采用系统调用pipe来完成管道的创建,相当于直接用了系统的管道,而windows上用的是loopback,同样是为了达到通知的目的,windows与与solaris采用了不同的方案。至于windows为什么不采用管道来实现,留个疑问??

Java线程和多线程(八)——Thread Dump

Java的Thread Dump就是列出JVM中所有激活状态的线程。Java Thread DumpJava Thread Dump在分析应用性能瓶颈和死锁的时候,是非常有效的。下面将介绍多种不同的方...
  • EthanWhite
  • EthanWhite
  • 2016年09月27日 22:54
  • 6539

Java NIO系列4:通道和选择器

前言今天加班回来,终于有时间继续更新NIO的文章了。在前一篇文章我们讲解了缓冲区的知识,并通过代码演示了如何使用缓冲区的API完成一些操作。这里要讲的通道于缓冲区关系密切,简单来说,缓冲区是填充数据的...
  • u011116672
  • u011116672
  • 2016年05月28日 23:21
  • 9715

Java NIO使用及原理之--选择器Seclector

转载自:李会军•宁静致远 在上一篇文章中介绍了关于缓冲区的一些细节内容,现在终于可以进入NIO中最有意思的部分非阻塞I/O。通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可...
  • zcxwww
  • zcxwww
  • 2016年05月09日 19:06
  • 835

Java的NIO之Selectors的使用

就前面的知识来说,对于一般的NIO应用足够了,那为什么推出Selector呢?Selectors(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件...
  • u010853261
  • u010853261
  • 2016年12月05日 13:28
  • 524

Java Nio注意事项

Selector  :   public abstract class Selectorextends Object SelectableChannel 对象的多路复用器。 可...
  • wangxi969696
  • wangxi969696
  • 2012年03月14日 16:20
  • 1935

《Java NIO》:Channel and Buffer (通道和缓冲区)

《Java NIO》:Channel and Buffer (通道和缓冲区)从今天开始,自己将会了解下Java NIO的相关知识,以及会看下相关的类库源码。和往常自己学习新知识之前,自己都会阅读网...
  • u010412719
  • u010412719
  • 2016年10月08日 19:46
  • 980

Java用NIO写一个通讯服务端

Java NIO Java NIO非堵塞技术实际是采取Reactor模式,或者说是Observer模式为我们监察I/O端口,如果有内容进来,会自动通知我们,这样,我们就不必开启多个线程死等,从外界看...
  • lh15871815717
  • lh15871815717
  • 2014年12月19日 11:43
  • 7689

基于nio的java网络通信框架介绍和选择

Java NIO框架Mina、Netty、Grizzly介绍与对比 Mina Mina(Multipurpose Infrastructure for Network Applications) ...
  • aosnowasp
  • aosnowasp
  • 2014年12月08日 23:54
  • 997

《Java NIO》学习笔记四 选择器(Selector)

一、选择器基础 选择器(Selector): 选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。 可选择通道(Select...
  • u010567606
  • u010567606
  • 2014年09月18日 16:36
  • 2036

NIO学习笔记——选择器(selectors)

选择器(Selector) 选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。当这么做的时候,可以选择将被激发的线程挂起,直到...
  • fuyuwei2015
  • fuyuwei2015
  • 2017年06月26日 22:26
  • 317
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:JAVA NIO 选择器
举报原因:
原因补充:

(最多只允许输入30个字)