Java NIO 类库Selector机制解析(续)

简介:
在前些天的《 Java NIO类库Selector机制解析 》文章中,我们知道了下面的事情:
 
1)SunJVM在实现Selector上,在LinuxWindows平台下的细节。
2)Selector类的wakeup()方法如何唤醒阻塞在select()系统调用上的细节。
 
先给大家做一个简单的回顾,在Windows下,SunJava虚拟机在Selector.open()时会自己和自己建立loopbackTCP链接;在Linux下,Selector会创建pipe。这主要是为了Selector.wakeup()可以方便唤醒阻塞在select()系统调用上的线程(通过向自己所建立的TCP链接和管道上随便写点什么就可以唤醒阻塞线程)
 
我们知道,无论是建立TCP链接还是建立管道都会消耗系统资源,而在Windows上,某些Windows上的防火墙设置还可能会导致JavaSelector因为建立不起loopbackTCP链接而出现异常。
 
而在我的另一篇文章《 GDB调试Java程序 》中介绍了另一个Java的解释器——GNUgij,以及编译器gcj,不但可以比较高效地运行Java程序,而且还可以把Java程序直接编译成可执行文件。
 
GNU的之所以要重做一个Java的编译和解释器,其一个重要原因就是想解释SunJVM的效率和资源耗费问题。当然,GNUJava编译/解释器并不需要考虑太多复杂的平台,他们只需要专注于Linux和衍生自Unix System V的操作系统,对于开发人员来说,离开了Windows,一切都会变得简单起来。在这里,让我们看看GNUgij是如何解释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命令查看具体的系统调用时能够快速定位。之后调用的是Selectorwakeup()方法来唤醒侦听线程。
 
接下来,我们可以通过两种方式来编译这个程序:
1)使用gcj或是sunjavac编译成class文件,然后使用gij解释执行。
2)使用gcj直接编译成可执行文件。
(无论你用那种方法,都是一样的结果,本文使用第二种方法,关于gcj的编译方法,请参看我的《 GDB调试Java程序 》)
 
编译成可执行文件后,执行程序时,使用lsof命令,我们可以看到没有任何pipe的建立。可见GNU的解释更为的节省资源。而对于一个UnixC程序员来说,这意味着如果要唤醒select()只能使用pthread_kill()来发送一个信号了。下面就让我们使用strace命令来验证这个想法。
 
下图是使用strace命令来跟踪整个程序运行时的系统调用,我们利用我们的输出的“wakeup the select”字符串快速的找到了wakeup的实际系统调用。
 
 
果然,我们可可以看到,tgkill(5829, 5831, SIGHUP)这个系统调用,第一个参数是“源线程id”,第二个参数是“目的线程id”,第三个参数是“信号SIGHUP”。通过每一行前面的线程号我们可以看到紧接着tgkill后面的5831线程的“… select resumed”字样。
 
可见,GNU的确是使用最为传统的pthread_killkill系统调用向阻塞线程发信号的方法来实现Selector.wakeup()的,这也证明了GNUJava编译/解释器是不会消耗系统文件描述符的。而我们也终于看到了回归经典的Java实现机制。









本文转自 haoel 51CTO博客,原文链接:http://blog.51cto.com/haoel/124570,如需转载请自行联系原作者

目录
相关文章
|
15天前
|
Java
Java中ReentrantLock释放锁代码解析
Java中ReentrantLock释放锁代码解析
25 8
|
1月前
|
开发框架 Java API
java反射机制的原理与简单使用
java反射机制的原理与简单使用
17 1
|
1天前
|
Java 数据库连接
深入理解Java异常处理机制
【4月更文挑战第24天】本文将探讨Java中的异常处理机制,包括异常的概念、分类、捕获和抛出等方面。通过深入了解异常处理机制,可以帮助我们编写更加健壮的程序,提高代码的可读性和可维护性。
|
1天前
|
Java
Java输入输出流详细解析
Java输入输出流详细解析
Java输入输出流详细解析
|
1天前
|
存储 Java C++
Java集合篇之深度解析Queue,单端队列、双端队列、优先级队列、阻塞队列
Java集合篇之深度解析Queue,单端队列、双端队列、优先级队列、阻塞队列
8 0
|
6天前
|
监控 Java 开发者
深入理解 Java 网络编程和 NIO
【4月更文挑战第19天】Java网络编程基于Socket,但NIO(非阻塞I/O)提升了效率和性能。NIO特点是非阻塞模式、选择器机制和缓冲区,适合高并发场景。使用NIO涉及通道、选择器和事件处理,优点是高并发、资源利用率和可扩展性,但复杂度、错误处理和性能调优是挑战。开发者应根据需求选择是否使用NIO,并深入理解其原理。
|
8天前
|
Java API 数据库
深入解析:使用JPA进行Java对象关系映射的实践与应用
【4月更文挑战第17天】Java Persistence API (JPA) 是Java EE中的ORM规范,简化数据库操作,让开发者以面向对象方式处理数据,提高效率和代码可读性。它定义了Java对象与数据库表的映射,通过@Entity等注解标记实体类,如User类映射到users表。JPA提供持久化上下文和EntityManager,管理对象生命周期,支持Criteria API和JPQL进行数据库查询。同时,JPA包含事务管理功能,保证数据一致性。使用JPA能降低开发复杂性,但需根据项目需求灵活应用,结合框架如Spring Data JPA,进一步提升开发便捷性。
|
13天前
|
Java
Java 15 神秘登场:隐藏类解析未知领域
Java 15 神秘登场:隐藏类解析未知领域
17 0
|
13天前
|
安全 Java 编译器
接口之美,内部之妙:深入解析Java的接口与内部类
接口之美,内部之妙:深入解析Java的接口与内部类
35 0
接口之美,内部之妙:深入解析Java的接口与内部类
|
19天前
|
安全 Java 调度
深入理解Java中的线程安全与锁机制
【4月更文挑战第6天】 在并发编程领域,Java语言提供了强大的线程支持和同步机制来确保多线程环境下的数据一致性和线程安全性。本文将深入探讨Java中线程安全的概念、常见的线程安全问题以及如何使用不同的锁机制来解决这些问题。我们将从基本的synchronized关键字开始,到显式锁(如ReentrantLock),再到读写锁(ReadWriteLock)的讨论,并结合实例代码来展示它们在实际开发中的应用。通过本文,读者不仅能够理解线程安全的重要性,还能掌握如何有效地在Java中应用各种锁机制以保障程序的稳定运行。