NIO(JDK1.4)--选择器Selector

先看下面例子:
public  class  SelectSockets {
     private  ByteBuffer buffer = ByteBuffer.allocateDirect( 1024 );
     public  static  void  main(String[] argv)  throws  Exception {
         new  SelectSockets().go();
     }
     public  void  go()  throws  Exception {
         int  port =  1234 ;
         ServerSocketChannel serverChannel = ServerSocketChannel.open();
         ServerSocket serverSocket = serverChannel.socket();
         serverSocket.bind( new  InetSocketAddress(port));
         serverChannel.configureBlocking( false );
         Selector selector = Selector.open();
         /*
          * 把serverChannel注册到选择器里,这里没有绑定某个对象,register可以通过第三个参数指定要绑定的对象,比如,绑定一个
          * ByteBuffer对象等,也可以通过key.attach (myObject)绑定,其中key是serverChannel.register(...)的返回对象,
          * 这里接受连接和读取数据都只有一个通道,无需加以区分,如果有多个,可以通过绑定对应的对象加以区分
          */
         serverChannel.register(selector, SelectionKey.OP_ACCEPT);
         while  ( true ) {
             int  n = selector.select(); //阻塞一直等待,也可以通过select的参数设置阻塞的最长时间
             if  (n ==  0 ) {
                 continue ;
             }
             Iterator it = selector.selectedKeys().iterator();
             while  (it.hasNext()) {
                 SelectionKey key = (SelectionKey)it.next();
                 it.remove();
                 if  (key.isAcceptable()) {
                     ServerSocketChannel server = (ServerSocketChannel) key.channel();
                     SocketChannel channel = server.accept();
                     if  (channel ==  null )
                         continue ;
                     channel.configureBlocking( false );
                     channel.register(selector, SelectionKey.OP_READ);
                     buffer.clear();
                     buffer.put( "Hi there!\r\n" .getBytes());
                     buffer.flip();
                     channel.write(buffer);
                 } else  if  (key.isReadable()) {
                     readDataFromSocket(key);
                 }
             }
         }
     }
     protected  void  readDataFromSocket(SelectionKey key)  throws  Exception {
         SocketChannel socketChannel = (SocketChannel) key.channel();
         int  count;
         buffer.clear();
         while  ((count = socketChannel.read(buffer)) >  0 ) {
             buffer.flip();
             while  (buffer.hasRemaining()) {
                 socketChannel.write(buffer); //这里只是简单地把收到的数据发送回去
             }
             buffer.clear();
         }
         if  (count <  0 ) {
             /*
              * 当收到EOF时,count<0,可以调用close方法关闭该TCP连接,当然,也可以在收到某条消息时或需要的时候调用close方法关闭该连接,
              * 调用close之后,与之关联的selectionKey也随之失效, 同时该selector会在下一次调用select方法被注销掉
              */
             socketChannel.close();
         }
     }
}

选择器对象是线程安全的,如某个线程调用selector对象的close方法时,另一个线程正在执行该selector对象的select方法,则close需要等待select方法执行完才可执行。selector对象虽然是线程安全的,但它们包含的键集合不是,通过 keys( )和 selectKeys( )返回的键的集合是 Selector对象内部的私有的 Set 对象集合的 直接引用。这些集合可能在任意时间被改变。已注册的键的集合是只读的。如果您试图修改它,那么得到的将是一个 java.lang.UnsupportedOperationException,但是当您在观察它们的时候,它们可能发生了改变,您仍然会遇到麻烦。Iterator 对象是快速失败的(fail-fast):如果底层的 Set 被改变了,它们将会抛出 java.util.ConcurrentModificationException,因此如果您期望在多个线程间共享选择器和/或键,请对此做好准备。您可以直接修改选择键,但请注意您这么做时可能会彻底破坏另一个线程的Iterator。 如果在多个线程并发地访问一个选择器的键的集合的时候存在任何问题,您可以采取一些步骤 来合理地同步访问。在执行选择操作时,选择器在 Selector 对象上进行同步,然后是已注册的键的集合,最后是已选择的键的集合,按照这样的顺序。已取消的键的集合也在选择过程的的第 1 步和第 3步之间保持同步(当与已取消的键的集合相关的通道被注销时)。 在多线程的场景中,如果您需要对任何一个键的集合进行更改,不管是直接更改还是其他操作 带来的副作用,您都需要首先以相同的顺序,在同一对象上进行同步。锁的过程是非常重要的。如果竞争的线程没有以相同的顺序请求锁,就将会有死锁的潜在隐患。如果您可以确保否其他线程不会同时访问选择器,那么就不必要进行同步了。 
Selector 类的 close( )方法与 select( )方法的同步方式是一样的,因此也有一直阻塞的可能性。在选择过程还在进行的过程中,所有对close( )的调用都会被阻塞,直到选择过程结束,或者执行选择的线程进入睡眠。在后面的情况下,执行选择的线程将会在执行关闭的线程获得锁时立即被唤醒,并关闭选择器。


使用线程池来为通道提供服务
这里的线程池是自己实现的,而不是利用Java自带的线程池,利用wait等待任务,当有任务到来时调用notify唤醒一个线程为其服务,下面代码的类继承了上面例子的类。
public  class  SelectSocketsThreadPool  extends  SelectSockets {
     private  static  final  int  MAX_THREADS =  5 ;
     private  ThreadPool pool =  new  ThreadPool(MAX_THREADS);
     public  static  void  main(String[] argv)  throws  Exception {
         new  SelectSocketsThreadPool().go();
     }
     @Override
     protected  void  readDataFromSocket(SelectionKey key)  throws  Exception {
         WorkerThread worker = pool.getWorker();
         if  (worker ==  null ) {
             // No threads available. Do nothing. The selection loop will keep calling this method 
             //until a thread becomes available. This design could be improved.
             return ;
         }
         worker.serviceChannel(key);
     }
     private  class  ThreadPool {
         List<WorkerThread> idle =  new  LinkedList<WorkerThread>();
         ThreadPool( int  poolSize) {
             for  ( int  i =  0 ; i < poolSize; i++) {
                 WorkerThread thread =  new  WorkerThread( this );
                 thread.setName( "Worker"  + (i +  1 ));
                 thread.start();
                 idle.add(thread);
             }
         }
         WorkerThread getWorker() {
             WorkerThread worker =  null ;
             synchronized  (idle) {
                 if  (idle.size() >  0 ) {
                     worker = (WorkerThread) idle.remove( 0 );
                 }
             }
             return  (worker);
         }
         void  returnWorker(WorkerThread worker) {
             synchronized  (idle) {
                 idle.add(worker);
             }
         }
     }
     private  class  WorkerThread  extends  Thread {
         private  ByteBuffer buffer = ByteBuffer.allocate( 1024 );
         private  ThreadPool pool;
         private  SelectionKey key;
         WorkerThread(ThreadPool pool) {
             this .pool = pool;
         }
         public  synchronized  void  run() {
             System.out.println( this .getName() +  " is ready" );
             while  ( true ) {
                 try  {
                     // Sleep and release object lock
                     this .wait();
                 catch  (InterruptedException e) {
                     e.printStackTrace();
                     // Clear interrupt status
                     this .interrupted();
                 }
                 if  (key ==  null ) {
                     continue ;
                 }
                 System.out.println( this .getName() +  " has been awakened" );
                 try  {
                     drainChannel(key);
                 catch  (Exception e) {
                     System.out.println( "Caught '"  + e +  "' closing channel" );
                     try  {
                         key.channel().close();
                     catch  (IOException ex) {
                         ex.printStackTrace();
                     }
                     key.selector().wakeup();
                 }
                 key =  null ;
                 // Done. Ready for more. Return to pool
                 this .pool.returnWorker( this );
             }
         }
         synchronized  void  serviceChannel(SelectionKey key) {
             this .key = key;
             /**
              * 由于执行完readDataFromSocket方法,或者说执行完serviceChannel方法时,选择过程的线程将重新循环并几乎立即再次调用 select(),
              * 这时通道中的数据实际上还没有取出,因为取数据在另一个线程中进行,故选择器重复认为数据可读,从而重复地调用readDataFromSocket方法,
              * 所以,这里对键的interest集合修改,将interest(感兴趣的操作)从读取就绪(read-rreadiness)状态中移除
              */
             key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
             this .notify();
         }
         void  drainChannel(SelectionKey key)  throws  Exception {
             SocketChannel channel = (SocketChannel) key.channel();
             int  count;
             buffer.clear();
             while  ((count = channel.read(buffer)) >  0 ) {
                 buffer.flip();
                 while  (buffer.hasRemaining()) {
                     channel.write(buffer);
                 }
                 buffer.clear();
             }
             if  (count <  0 ) {
                 channel.close();
                 return ;
             }
             //当工作线程结束为通道提供的服务时,它将再次更新键的ready集合,将interest重新放到读取就绪集合中。
             key.interestOps(key.interestOps() | SelectionKey.OP_READ);
             /**
              * 在选择器上显式地调用 wakeup(),因为主线程可能在select()处阻塞,这将使它继续执行。
              * 这个选择循环会再次执行一个轮回(可能什么也没做)并带着被更新的键重新进入select()。这样做的原因是:
              * key所对应的通道在取数据的过程中被从对读感兴趣的集合中删掉,有可能在从buffer中取出数据的时候又有新的数据到来,
              * 所以在把该通道加到读集合中之后,主动唤醒选择器,让选择器检测是否有读就绪,否则选择器可能一直阻塞,而实际上数据已经就绪了
              */
             key.selector().wakeup();
         }
     }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值