Java的NIO以及线程并发

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

while(somecondition)
{

    int n = selector.select(TIMEOUT);
    if(n == 0) continue;
    for (Iterator iter = selector.selectedKeys().iterator(); iter.hasNext();)
    {
    if (key.isAcceptable())
        doAcceptable(key);
    if (key.isConnectable())
        doConnectable(key);
    if (key.isValid() && key.isReadable())
        doReadable(key);
    if (key.isValid() && key.isWritable())
        doWritable(key);
  iter.remove();
} 

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

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

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

本文出自seven的测试人生公众号最新内容请见作者的GitHub页:http://qaseven.github.io/

时间: 2024-09-23 12:05:01

Java的NIO以及线程并发的相关文章

Android多线程研究(7)Java5中的线程并发库

从这一篇开始我们将看看Java 5之后给我们添加的新的对线程操作的API,首先看看api文档: java.util.concurrent包含许多线程安全.测试良好.高性能的并发构建块,我们先看看atomic包下的AtomicInteger. import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerTest { private static AtomicInteger data = new Atomic

Java 6中的线程优化真的有效么?

介绍 - Java 6中的线程优化 Sun.IBM.BEA和其他公司在各自实现的Java 6虚拟机上都花费了大量的精力 优化锁的管理和同步.诸如偏向锁(biased locking).锁粗化(lock coarsening).由逸出(escape)分析产生的锁省略.自适应自旋锁(adaptive spinning)这些特性,都是通过在应用程序线程之间更高效地共享数据,从而提 高并发效率.尽管这些特性都是成熟且有趣的,但是问题在于:它们的承诺真的 能实现么?在这篇由两部分组成的文章里,我将逐一探究

java内存模型与线程(转) good

java内存模型与线程 参考 http://baike.baidu.com/view/8657411.htm  http://developer.51cto.com/art/201309/410971_all.htm http://www.cnblogs.com/skywang12345/p/3447546.html 计算机的CPU计算能力超强,其计算速度与 内存等存储 和通讯子系统的速度相比快了几个数量级, 数据加载到内存中后,cpu处理器运算处理时,大部分时间花在等待获取去获取磁盘IO.网络

了解Java 8功能如何让并发性编程变得更容易

在期待已久的 Java 8 版本中,并发性方面已实现了许多改进,其中包括在java.util.concurrent 层级中增加新的类和强大的新并行流 功能.设计流的目的是与lambda 表达式 共同使用,Java 8 的这项增强也使得日常编程的其他很多方面变得更加简便.(参见介绍 Java 8 语言的 指南文章,了解对于 lambda 表达式的介绍及相关 interface改动.) 本文首先介绍了新的 CompletableFuture 类如何更好地协调异步操作.接下来,我将介绍如何使用并行流(

在Java内存模型中测试并发程序代码_java

让我们来看看这段代码:   import java.util.BitSet; import java.util.concurrent.CountDownLatch; public class AnExample { public static void main(String[] args) throws Exception { BitSet bs = new BitSet(); CountDownLatch latch = new CountDownLatch(1); Thread t1 =

JAVA语言规范:线程和锁

JAVA语言规范:线程和锁 概述: 前面章节的大多数讨论,都是关于通过单线程一次执行单个语句或者表达式.而JAVA虚拟机可以支持多线程同时执行.这些线程可以独立执行代码操作,而操作后所产生的值会保留在共享内存当中.单处理器和多处理器都能支持多线程,它们都是通过分配CPU时间片来执行代码. 线程由Thread类表示.用户创建线程的唯一方式是创建此类的一个对象:每个线程和这样的一个对象相关.当在相应的Thread对象上调用start()方法是,一个线程将启动. 线程的行为,特别是当不正确同步时,容易

java的nio之:java的nio系列教程之概述

一:java的nio的核心组件?Java NIO 由以下几个核心部分组成: ==>Channels ==>Buffers ==>Selectors 虽然Java NIO 中除此之外还有很多类和组件,但在我看来,Channel,Buffer 和 Selector 构成了核心的API.其它组件,如Pipe和FileLock,只不过是与三个核心组件共同使用的工具类.因此,在概述中我将集中在这三个组件上.其它组 件会在单独的章节中讲到. Channel 和 Buffer 基本上,所有的 IO 在

服务器-java 关于NIO实现UDP数据传输问题 ,急谢谢,C币不足请不要介意

问题描述 java 关于NIO实现UDP数据传输问题 ,急谢谢,C币不足请不要介意 各位大侠好,小弟想问一下问题,搞了一两天没有搞明白的.因为要实现一个UDP传输服务端,于是在网上找了很多资料然后就写了一个.但是写好之后发现有两个很严重的问题,希望各位大哥给点意见或者思路去解决.问题一:启动服务端,同时也启动客户端,客户端传输数据服务器正常接收,但是断开客户端后,再启动客户端,服务器就收不到任何客户端发送的消息,好像是服务器关闭了UDP一样,但是重启服务器后(重新打开UDP)客户端既可以发送信息

Java基础-创建Java程序中的线程池

程序|创建 线程是Java的一大特性,它可以是给定的指令序列.给定的方法中定义的变量或者一些共享数据(类一级的变量).在Java中每个线程有自己的堆栈和程序计数器(PC),其中堆栈是用来跟踪线程的上下文(上下文是当线程执行到某处时,当前的局部变量的值),而程序计数器则用来跟踪当前线程正在执行的指令. 在通常情况下,一个线程不能访问另外一个线程的堆栈变量,而且这个线程必须处于如下状态之一: 1.排队状态(Ready),在用户创建了一个线程以后,这个线程不会立即运行.当线程中的方法start()被调