最近仔细研究了一下Java的NIO以及线程并发,搞清了点思路,特做笔记以下(NIO篇)

[转]http://www.cnblogs.com/feidao/archive/2005/07/15/193788.html

        由于前段时间的项目须要写一些高性能服务器,结果写出来的结果是五花八门,咱们要求使用NIO编写异步服务器,可是居然有人把NIO硬生生地写成同步的,还写成了一个单道批处理,线程调度、通讯、同步操做,尤如天马行空,看不出一点架构,典型的面条代码,极度晕倒,不得不下定决心,将IO部分与线程调度部分隔离出来。
        为此,狠下心来,仔细研究了一下nio机制和dl的util.concurrent包。

1、NIO的出现
        NIO是JDK1.4里面才出现的东东,他给你们带来的最大好处是异步socket。其它file,pipe暂时就很少谈了。
        在JDK1.4出现以前,若是你须要编写一个Java服务器,为了实现异步操做,你必须为每一个链接请求生成一个Java线程,当链接请求不少时,线程的调度,上下文切换,所付出的代价是很是昂贵,并且因为Java是跨平台的,各个平台对线程的支持并不相同,性能也不相同,所以传统的Java服务器编程架构是低效的且代价贵,dl大侠写了个util.concurrent包后,总算是减轻了线程调度给java程序员带来的痛苦,可是相比之与C、C++写出来的服务器,java服务器在性能要求很高的状况下,基本上没有什么竞争力,甚至是入围的权利的都没有。

2、异步socket的实现
       NIO出现后,好像让java的程序员有了杨眉吐气的机会,怎么个吐气法,当时你们是个什么感觉,俺是不知道,由于当时俺不搞java,对java的认识有限。
       NIO是一个基于事件的IO架构,最基本的思想就是:有事件我通知你,你再去作你的事情,没事件时你大能够节约大把时间去作其它任何事情。并且NIO的主线程only one,不像传统的模型,须要N个线程去,也减轻了JVM的工做量,使得JVM处理任务时显得更加高效。
       刚开始接触NIO时,被N层的Channel架构、网上铺天盖地的好评给镇住了,想一想也应当是个很成熟的产品了,网上资料这么多,抄一抄Jetty、Tomcat以及其它一些牛B的源代码,基本上就能搞定了,此时没有想到你们受同步的影响这么深,也没有想到连最基本的异步概念都没有搞清楚就去写代码,搞出一堆的问题来(这是后话,后面再说)。
       如今研究了NIO之后,发现NIO实际上在Java中作的工做是很简单,就是将事件进行收集和分发,咱们结合一个经典的调用例子来讲明这个问题,我就不从NIO的基本使用提及了,你们能够查其它的资料,网上一大把。
        当Channel注册至Selector之后,咱们的最经典的调用方法,是这样子的。html

 1 while (somecondition)
 2 {
 3        int n = selector.select(TIMEOUT);
 4        if(n == 0continue;
 5        for (Iterator iter = selector.selectedKeys().iterator(); iter.hasNext();)
 6        {
 7        if (key.isAcceptable())
 8            doAcceptable(key);
 9        if (key.isConnectable())
10            doConnectable(key);
11        if (key.isValid() && key.isReadable())
12            doReadable(key);
13        if (key.isValid() && key.isWritable())
14            doWritable(key);
15      iter.remove();
16    }

17}
            这只是个小例子啊,什么异常我就懒得抓了。             nio中取得事件通知,就是在selector的select事件中完成的,在selector事件时有一个线程,这个线程具体的处理简单点说就是:向操做系统询问,selector中注册的Channel&&SelectionKey的偶对各类事件是否有发生,若是有则添加到selector的selectedKeys属性Set中去,并返回本次有多少个感兴趣的事情发生。程序员发现这个值>0,表示有事件发生,立刻迭代selectedKeys中的SelectionKey,根据Key中的表示的事件,来作相应的处理。             实际上,这段说明代表了异步socket的核心,即异步socket不过是将多个socket的调度(或者还有他们的线程调度)所有交给操做系统本身去完成,异步的核心Selector,不过是将这些调度收集、分发而已。由于操做系统的socket、线程调度再咋D也比你JVM中要强,效率也高。             并且就算jvm作的和操做系统同样好,性能同样高(固然这是不现实的),使用异步socket你至少也节约了一半的系统消耗,想一想假定操做系统自己也是使用线程来维护N个socket链接,在传统的java编程中,你还必须为这些socket还多起一个java线程,那至少是2N个线程,如今只须要N+1。在高并发的状况下,你本身去想吧。         懂了这个道理,异步socket也就好写了,也不会搞得思路混乱了。 3、 异步Socket中应当注意的事情     3.1 读           异步socket最基本的理念就是事件通知,前面也说了,有事件通知你了,你才该作你应当作的事情。在异步socket中当注册了一个OP_READ事件后,你就等着Selector通知你吧,若是没有通知你,你在家睡大觉都行。          在这里,咱们有人出现的错误就是受同步的影响,本身去主动读,并且还搞出了多线程,若是仔细考虑一下,就不会出现这个问题了。同步socket中,调用read方法读取IO中的数据时,一般状况下若是没有数据read方法会阻塞,且是同步的,因此当多个线程同时访问时,read方法是线程安全的。          而在异步下就不一样,异步是不会阻塞的,有什么就返回什么,你主动去读,只要有数据,你就能够拿走,在多线程的状况下,也许你是想让第一个线程读取,but此时来数据时正好是线程2读到了,那线程2就高高兴兴的拿去,而线程1还在苦苦等待,这样致使数据混乱不说,若是后面不再来数据了,线程1就是死循环啦。         2. 写         在异步socket中,写是惟一一个主动点的操做,可是也不能直接去写Channel,而是应当先把自身注册为OP_WRITABLE,这时Selector就会发现你的存在,并把给发一个write事件,你这时后就能够写了,不过这时候有个小小的技巧,就是你执行写操做以前,请取消掉你的写注册,不然你的cpu确定是100%。         3. 等待         在传统的服务器编程中,因为对于每一个请求都是产生的一个线程,所以你在你每一个请求线程中wait也好,sleep也好,不会影响别人。可是异步不一样,他的主线程只有一个,基本上每一个处理都是线性的,也就是说处理完第一个,而后才能处理第二个,所以nio是一个极好的处理短链接的架构。         咱们如今出现的问题是,有人受同步的影响,没有搞清异步是如何处理,居然在方法处理中用上sleep,并且一等仍是3秒,这意味着什么,3秒才能处理一个请求,My god,我要一个3秒才能处理一个请求的服务器干吗啊,仍是60年代啊:(         若是出现这样的须要等待的状况,应当另起一个线程(推荐使用线程池)去完成这个“长”时间的任务,或者将其它交给一个消息队列,经过发消息的方式将给别人去完成也行,客户端能等,你服务器怎么也能等呢?写出这样的代码,基本上一个服务器也就废了。         这blog我发在我的的心情随笔中,发点牢骚,实在是以为对于有些人的思路以为有些难以想象,不吐不快。
相关文章
相关标签/搜索