jclown

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  3 Posts :: 0 Stories :: 6 Comments :: 0 Trackbacks

2010年9月19日 #

  Java nio从jdk1.4开始引入进来的。通常听到比较多的说法是: 你还没有用nio啊,你“out”啦;nio不阻塞,传统的I/O都是阻塞的,效率很低...... 如果你的系统在使用传统I/O上已经工作得很好且没有更多的性能等方面的要求,千万不要盲目地使用nio来重构(也许在你的系统的上下文环境中,使用nio所带来其它方面的复杂性会提高,处理增多,整体效率并未提高),这至少不符合敏捷开发的态度,简单说,不要为了nio而nio。当然这不阻碍我们对nio的认识。

   Java nio是在Java编程中对I/O的另外一种高级的抽象方式。其对I/O的使用方式也更加贴近操作系统使用I/O的方式:通道和字节缓冲。一般的用法可以参考<<Thinking in java>>。这里我们谈谈使用nio进行网络通信编程的特点。基本的Java socket编程对小规模的系统可以很好的工作,但是在资源不扩展而用户请求不断增大的情况下,系统劣化的比率很大。我们通常会面临这样一些这些问题:

1、thread-per-request 的方式,这样会有更多线程维护和切换的系统开销,同时在系统扩展性方面受到限制。

2、针对问题1,我们可以采用线程池的方式来节省线程创建、维护以及切换的开销,但是这样也会限制系统可以同时处理客户端的数量,至少对一些长连接协议来说是这样。此时增加线程池的大小是不能再提高系统性能的,而只会增加系统更多的线程开销。

3、传统的socket在读/写数据以及建立连接上都是阻塞式的(没有数据可读可能会阻塞;没有足够空间缓存传输的数据可能阻塞;服务端的accept方法以及socket构造函数都会阻塞等待,直到连接建立) 。这些特性会降低整个系统对CPU的利用率以及系统的活跃性,降低系统对客户请求的响应性及响应时间,在某些情况下是不可接受的甚至会带来灾难性的结果。在这样的情形下,我们要解决cpu利用率、线程活跃性以及提高系统吞吐量,我们势必做很多额外的工作且相当复杂。

4、由于传统socket的阻塞特性,每个对等端都在阻塞等待另外一端完成相关处理,这样势必增大死锁的风险。

5、当然为了提高系统的活跃性,传统的socket在accept、构造socket以及read方法上都增加了超时控制的处理以及使用socket.close方法来打断一些阻塞操作,而网络总是不确定的,设置这些超时时间的设置在具体的上下文环境中的取值也是比较复杂的。简而言之,为了提高系统的活跃性会增大系统的复杂度。

     那么,Java nio真的就是Java socket编程解决以上若干问题的灵丹妙药吗?答案是否定的。

     nio的非阻塞特性是nio最大的特点,所谓非阻塞无非就是将通信的信道设置成为非阻塞的情况下,对该信道的所有操作都是会立即返回的。如数据读取,没有数据的时候会返回0而不是阻塞在信道上。这样就增大了系统的活跃性,使得系统不必浪费资源的I/O操作上,系统可以更好的利用cpu资源做一些其它处理,在某些场景下会提高系统的响应性以及吞吐量。

     同时nio在网络通信的中采用的网络I/O事件驱动的方式,即操作系统对用户感兴趣的通信信道及其上面的I/O事件进行监听并通知应用程序。这实际上也是观察者模式的一种应用,在这样的背景下,问题4中的死锁风险几乎就没有了。

     如果你开发的服务端不是迭代服务器(顺序化处理每个客户请求),那么对于问题1、2在nio服务器中也是一样的。换句话说,我们开发的并发处理服务器为了实现对各种网络I/O事件的处理,并对每种我们所关心的网络I/O事件进行及时响应,采用thread-per-request或者线程池的方式是无法避免的。所以很多人会感觉nio的网络编程结构与非nio的网络编程结构实际是一样的,accept换成了select,监听连接入站编程了监听信道上感兴趣的I/O事件,为了提高系统的处理能力,还是要启动异步的线程来处理信道上的网络I/O事件,只是具体实现不一样而已。这说明nio并没有改变我们服务端程序编写的整体结构,只是在nio的环境下,我们的确可以去提高系统的活跃性、响应性和吞吐量。nio与非nio在读/写网络数据以及连接建立等网络操作上是没有多大区别的,这些因素主要都还是取决于网络状况、操作系统协议栈实现、应用自身处理网络数据的方式等多个方面。甚至,我们在享用nio的同时在一定程度上还会增大了网络编程的复杂度,因为数据的读写以及连接的建立等操作变得更加不确定,当然最终网络编程的复杂还是取决于协议的复杂度。

posted @ 2010-10-01 14:31 jclown 阅读(2551) | 评论 (2)编辑 收藏

     摘要: Apache MINA 以及网络NIO编程剖析。旨在用MINA的实现来诠释JAVA NIO 网路编程的深意。  阅读全文
posted @ 2010-09-19 21:59 jclown 阅读(2523) | 评论 (2)编辑 收藏

      ThreadLocal是线程变量的意思,不是本地线程。它可以为线程分配特有的空间(与线程自身的堆栈不同,是线程对象在内存中的一个Map),使得线程可以通过这个空间随时获取自己相关的上下文信息。

ThreadLocal 内幕

ThreadLocal很容易让使用者误解(以前我就误解了),因为在这个类中定义了一个静态的包级可见的内部类ThreadLocalMap,同时ThreadLocal对外提供了get和set方法。那么set的时候就是把当前线程对象作为ThreadLocalMap的key,把传入set方法的值作为value存储起来;get的时候通过获取当前线程对象,然后到ThreadLocalMap中取得对应的value。这种认识是表面的很容易理解的方式,但是也是错误的理解。

每个Thread类中都有ThreadLocal.ThreadLocalMap的两个成员,分别是threadlocals与inheritableThreadLocals。前者用来存储当前线程的相关数据信息,后者用来存储从父线程中继承或者说拷贝过来的相关数据信息。这两个区域也就是前面提到的线程的特别存储区域,但是如何操作它们?JDK中的设计并不是让用户直接操作当前线程来存取数据,而是让用户操作ThreadLocal来对当前线程的这两个Map进行存取(个人认为这样设计是出于安全性与易用性考虑,主要是Map中的key值需要固定才能取到同一对象),同时放置在这两个Map中的key值就是ThreadLocal对象自身,value由用户传入。


ThreadLocal 进阶

ThreadLocal为线程独占某些对象或者资源提供了新的手段,ThreadLocal与线程堆栈都是用来存取线程私有数据的区域,别的线程无法触碰这两个区域,但是二者确有不同的生命周期。这个可以这样理解,线程跨多个方法调用的时候,方法作为Frame不断的进栈和出栈(方法的调用与返回),同时出栈的时候方法中的临时变量数据会被回收,在这个线程的堆栈中消失,所以方法中的局部变量是无法在线程调用过程中穿透,除非是传递局部的引用类型变量,但是这也意味着方法接口都要额外接收新的参数(早期系统中传递JDBC数据库连接的方式)。而ThreadLocal不同,准确地说是线程中的ThreadLocalMap不同,线程对象中的ThreadLocalMap对象是伴随整个线程对象生命周期的,线程可以在任何需要的时候通过它来获取相关数据,这为线程独占数据提供了一种更加优雅的方式。只不过这一切都是通过ThreadLocal来操作的而已,同时ThreadLocal自身也作为不同线程中的ThreadLocalMap中的key来映射存入的value。

ThreadLocal 应用

public class ThreadWriter

{

   private static final ThreadLocal<FileWriter> local = new

ThreadLocal<FileWriter>();

    public void write(String s)

    {

       try

       {

           getFileWriter().write(s);

       }

       catch(IOException e)

       {

           //igore

       }

    }

    private FileWriter getFileWriter()

    {

       FileWriter fw = local.get();

       if (null == fw)

       {

           try

           {

fw = new FileWriter(Thread.currentThread().getName() + "-info.txt", true);

              local.set(fw);

           }

           catch(IOException e)

           {

             //igore

           }

       }

       return fw;

    }

}

这里说明几个问题:

1、 ThreadLocal对象只有一份,它将作为不同线程中的ThreadLocalMap对象的key,所映射的值是不同文件路径的FileWriter对象。只不过不同 线程中的ThreadLocalMap中的key值都一样,但是因为在不同的ThreadLocalMap中,不会有任何影响,反而这样的方式为每个线程中ThreadLocalMap对象中的key值生成省去了很多功夫,这是一个很精妙的设计,用户在间接使用ThreadLocalMap的时候,不需要感知key是什么,就可以方便地存取。

2、 一份ThreadLocal对象只能映射一种资源。如需要映射多种资源的话,定义多个ThreadLocal成员。

3、 local.set(fw); 这句代码表示将local自身作为key,fw作为value,存储在调用这行代码的线程中的ThreadLocalMap对象中。

FileWriter fw = local.get(); 这句代码表示将local自身作为key,从当前线程的ThreadLocalMap对象中取出属于自己独占的FileWriter对象。 

posted @ 2010-09-19 20:06 jclown 阅读(2214) | 评论 (2)编辑 收藏