Java的NIO以及线程并发

开发 后端
NIO出现后,好像让java的程序员有了杨眉吐气的机会,怎么个吐气法,当时大家是个什么感受,俺是不知道,因为当时俺不搞java,对java的认识有限。

一、NIO的出现

NIO是JDK1.4里面才出现的东东,他给大家带来的***好处是异步socket。其它file,pipe暂时就不多谈了。

在JDK1.4出现之前,如果你需要编写一个Java服务器,为了实现异步操作,你必须为每个连接请求生成一个Java线程,当连接请求很多时,线程的调度,上下文切换,所付出的代价是非常昂贵,而且由于Java是跨平台的,各个平台对线程的支持并不相同,性能也不相同,因此传统的Java服务器编程架构是低效的且代价贵,dl大侠写了个util.concurrent包后,总算是减轻了线程调度给java程序员带来的痛苦,但是相比之与C、C++写出来的服务器,java服务器在性能要求很高的情况下,基本上没有什么竞争力,甚至是入围的权利的都没有。

二、异步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以后,我们的最经典的调用方法,是这样子的。

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

这只是个小例子啊,什么异常我就懒得抓了。

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也就好写了,也不会搞得思路混乱了。

三、 异步Socket中应当注意的事情

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年代啊:(

如果出现这样的需要等待的情况,应当另起一个线程(推荐使用线程池)去完成这个“长”时间的任务,或者将其它交给一个消息队列,通过发消息的方式将给别人去完成也行,客户端能等,你服务器怎么也能等呢?写出这样的代码,基本上一个服务器也就废了。

原文链接:http://www.cnblogs.com/sunwei2012/archive/2010/02/11/1667501.html

【编辑推荐】

  1. 基于事件的NIO多线程服务器
  2. Java NIO的多路复用及reactor
  3. 在Java中使用NIO进行网络编程
  4. Java NIO非阻塞服务器示例
  5. 基于Java NIO的即时聊天服务器模型
责任编辑:林师授 来源: 玩玩乐乐的博客
相关推荐

2010-01-28 09:55:05

性能优化

2019-06-03 09:13:11

线程进程多线程

2018-09-19 14:53:02

NIOBIO运行

2020-09-04 10:29:47

Java线程池并发

2011-12-29 13:31:15

Java

2023-08-01 08:27:15

Java I/ONIO

2011-12-08 13:04:06

JavaNIO

2011-12-07 16:12:29

JavaNIO

2011-12-15 11:03:21

JavaNIO

2009-12-14 10:44:51

Java 7NIO2

2010-05-04 08:44:42

Java并发模型

2023-12-08 08:07:48

Java 21虚拟线程

2019-11-07 09:20:29

Java线程操作系统

2021-06-29 07:47:23

多线程协作数据

2019-09-16 08:45:53

并发编程通信

2024-03-12 13:11:20

powerjob单机线程

2019-02-26 11:15:25

进程多线程多进程

2023-10-18 09:27:58

Java编程

2011-12-15 09:40:06

Javanio

2011-12-07 14:57:44

JavaNIO
点赞
收藏

51CTO技术栈公众号