1)Sun的JVM在实现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实现机制。