<%@ page contentType="text/html; charset=gb2312"%> Java NIO 类库Selector机制解析(续)
网站公告:   ◆北天JAVA技术网热情为java爱好者服务,本网内容包括JAVA(JSP、servlet、EJB、webservice、j2ee、javabean、应用服务器、JavaScript),数据库(MYSQL、SQL Server、Sybase、Oracle、DB2、数据库综合知识),设计研究(设计模式、Struts、Spring、Hibernate、设计框架、设计综合知识),WEB2.0新技术(主要介绍AJAX),以及各种技术的入门、实例、例子等等,欢迎各位多来坐坐!◆  诚邀各位JAVA爱好者加盟!◆  本网站内容丰富,更新快,保证每周20篇以上!  
加入收藏
设为首页
联系站长
承接项目
  相关资源:网站首页 | 免费培训学院 | 技术论坛 | JAVA聊天室 | 作家专栏 | 开发工具 | 认证考试 | 会员俱乐部
  JAVA技术初学者园地 | jsp与servlet | javascript | Java源代码 | EJB | web service | 应用服务器 | JAVA综合知识
  设计研究设计模式 | 设计框架 | Struts | Spring | Hibernate | 开源项目 | 面向对象设计 | 设计综合知识
  数 据 库MYSQL | SQL Server | Sybase | Oracle | DB2 | Informix | Access | 数据库综合知识
  其他资源:AJAX新技术 | 网站开发 | ERP软件 | OA办公软件 | 商业智能BI | 开发综合知识 | 承接项目 | 项目试用

 
 
Java NIO 类库Selector机制解析(续)
     发布者: 发布时间:2008-05-07
在前些天的《Java NIO类库Selector机制解析》文章中,我们知道了下面的事情:



1)SunJVM在实现Selector上,在Linux和Windows平台下的细节。

2)Selector类的wakeup()方法如何唤醒阻塞在select()系统调用上的细节。



先给大家做一个简单的回顾,在Windows下,Sun的Java虚拟机在Selector.open()时会自己和自己建立loopback的TCP链接;在Linux下,Selector会创建pipe。这主要是为了Selector.wakeup()可以方便唤醒阻塞在select()系统调用上的线程(通过向自己所建立的TCP链接和管道上随便写点什么就可以唤醒阻塞线程)



我们知道,无论是建立TCP链接还是建立管道都会消耗系统资源,而在Windows上,某些Windows上的防火墙设置还可能会导致Java的Selector因为建立不起loopback的TCP链接而出现异常。



而在我的另一篇文章《用GDB调试Java程序》中介绍了另一个Java的解释器——GNU的gij,以及编译器gcj,不但可以比较高效地运行Java程序,而且还可以把Java程序直接编译成可执行文件。



GNU的之所以要重做一个Java的编译和解释器,其一个重要原因就是想解释Sun的JVM的效率和资源耗费问题。当然,GNU的Java编译/解释器并不需要考虑太多复杂的平台,他们只需要专注于Linux和衍生自Unix System V的操作系统,对于开发人员来说,离开了Windows,一切都会变得简单起来。在这里,让我们看看GNU的gij是如何解释Selector.open()和Selector.wakeup()的。



同样,我们需要一个测试程序。在这里,为了清晰,我不会例出所有的代码,我只给出我所使用的这个程序的一些关键代码。



我的这个测试程序中,和所有的Socket程序一样,下面是一个比较标准的框架,当然,这个框架应该是在一个线程中,也就是一个需要继承Runnable接口,并实现run()方法的一个类。(注意:其中的s是一个成员变量,是Selector类型,以便主线程序使用)





//生成一个侦听端

ServerSocketChannel ssc = ServerSocketChannel.open();

//将侦听端设为异步方式

ssc.configureBlocking(false);

//生成一个信号监视器

s = Selector.open();

//侦听端绑定到一个端口

ssc.socket().bind(new InetSocketAddress(port));

//设置侦听端所选的异步信号OP_ACCEPT

ssc.register(s,SelectionKey.OP_ACCEPT);



System.out.println("echo server has been set up ......");



while(true){

int n = s.select();

if (n == 0) { //没有指定的I/O事件发生

continue;

}

Iterator it = s.selectedKeys().iterator();

while (it.hasNext()) {

SelectionKey key = (SelectionKey) it.next();

if (key.isAcceptable()) { //侦听端信号触发

…… …… ……

…… …… ……

}

if (key.isReadable()) { //某socket可读信号

…… …… ……

…… …… ……

}

it.remove();

}

}







而在主线程中,我们可以通过Selector.wakeup()来唤醒这个阻塞在select()上的线程,下面是写在主线程中的唤醒程序:





new Thread(this).start();

try{

//Sleep 30 seconds

Thread.sleep(30000);

System.out.println("wakeup the select");

s.wakeup();

}catch(Exception e){

e.printStackTrace();

}





这个程序在主线程中,先启动一个线程,也就是上面那个Socket线程,然后休息30秒,为的是让上面的那个线程有阻塞在select(),然后打印出一条信息,这是为了我们用strace命令查看具体的系统调用时能够快速定位。之后调用的是Selector的wakeup()方法来唤醒侦听线程。



接下来,我们可以通过两种方式来编译这个程序:

1)使用gcj或是sun的javac编译成class文件,然后使用gij解释执行。

2)使用gcj直接编译成可执行文件。

(无论你用那种方法,都是一样的结果,本文使用第二种方法,关于gcj的编译方法,请参看我的《用GDB调试Java程序》)



编译成可执行文件后,执行程序时,使用lsof命令,我们可以看到没有任何pipe的建立。可见GNU的解释更为的节省资源。而对于一个Unix的C程序员来说,这意味着如果要唤醒select()只能使用pthread_kill()来发送一个信号了。下面就让我们使用strace命令来验证这个想法。



下图是使用strace命令来跟踪整个程序运行时的系统调用,我们利用我们的输出的“wakeup the select”字符串快速的找到了wakeup的实际系统调用。








果然,我们可可以看到,tgkill(5829, 5831, SIGHUP)这个系统调用,第一个参数是“源线程id”,第二个参数是“目的线程id”,第三个参数是“信号SIGHUP”。通过每一行前面的线程号我们可以看到紧接着tgkill后面的5831线程的“… select resumed”字样。



可见,GNU的确是使用最为传统的pthread_kill或kill系统调用向阻塞线程发信号的方法来实现Selector.wakeup()的,这也证明了GNU的Java编译/解释器是不会消耗系统文件描述符的。而我们也终于看到了回归经典的Java实现机制。

(转载文章请保留出处:北天JAVA技术网(www.java114.com))
 
更多精彩文章:
基于 JFace Text Framework 构建全功能代码编辑器:第 8 部分:Hyperlink
关于能提高Java代码可重用性的三个措施
Spring中的四种声明式事务的配置
JVM深度
初学者需要掌握的数据库设计词汇对照表
数据库结构中不可缺少的交易日志
 
最近评论:
        
鍥炲
        
标 题:   
内 容:   
 
                                  
 
免责声明:该文章由网友发表,如果对您造成侵权,请联系站长

首页 - 承接项目 - 网站地图 - 联系我们 -
版权所有北天JAVA技术工作室 ICP证号:粤ICP备06079815号