第2章 NIO入门

2.1 传统的BIO编程

  以服务器为例,在传统BIO模型下的服务器,每当一个新的请求到来的时候回分配一个线程去处理该请求,并且该线程在执行IO操作的时候会一直阻塞,知道IO操作完成或抛出异常才会返回。当网络情况不佳时,网络IO可能会耗费大量时间,那么就会同时有大量线程在服务器上阻塞着,很容易造成内存溢出。

  这种模型被称为同步阻塞模型,同步指的是只有等待线程IO操作完成该线程才会返回,阻塞指的是IO没有完成的时候一直等待。

  

2.1.1 服务端代码

  Server类,监听8080端口,在while循环里Server端阻塞在server.accept上,即等待请求传到8080端口上,从accept方法返回。后续new Thread是新建一个线程去处理请求。

public class TimeServer {
public static void main(String[] args) throws IOException {
int port = 8080;
ServerSocket server = null;

try {
server = new ServerSocket(port);
System.out.println("The server start in port "+port);
Socket socket = null;
while (true){
socket = server.accept();
Thread thread = new Thread(new TimeServerHandler(socket));
thread.start();
}


} catch (IOException e) {
e.printStackTrace();
} finally {
if (server!=null){
System.out.println("Time server close");
server.close();
server=null;
}
}
}
}

  执行代码用jstack打印线程状态,看到server线程在17行“停止”,即阻塞在17行等待请求传过来。这种阻塞就是BIO里的B,block,一个IO没有完成就一直卡在那里。

  有新的客户端接入新建线程执行处理方法,通过检查传过来的字符串是否是要求的“QUERY TIME ORDER”,如果是就返回当前服务器的时间,否则返回错误信息。

public class TimeServerHandler implements Runnable {
    private Socket socket;

    public TimeServerHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;

        try {
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(),true);
            String currentTime = null;
            String body = null;
            while (true){
                body = in.readLine();
                if (body == null){
                    break;
                }
                System.out.println("The time server receive order: "+body);
                currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)?new Date(System.currentTimeMillis()).toString():"BAD ORDER";
                out.println(currentTime);
            }
        } catch (IOException e) {
            e.printStackTrace();
            if (in!=null){
                try {
                    in.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
            if (out!=null){
                out.close();
                out = null;
            }

            if (socket!=null){
                try {
                    socket.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }finally {
                    socket=null;
                }
            }
        }
    }
}

   同时开启服务端和客户端后可以在控制台看到输出。

2.1.2 缺点

  • 每个请求都需要一个新建线程去处理,扛不住太大的并发,因为没有给线程数目设置瓶颈。
  • BIO会在网络不佳情况导致大量线程。

2.2 伪异步IO编程

2.2.1 代码

  用线程池代替不停的新建线程,好处是线程池是有界的,避免在极端情况下不停新建线程。

public class TimeServer_ThreadPool {
    public static void main(String[] args) {
        int port = 8080;
        ServerSocket server = null;
        try {
            server = new ServerSocket(port);
            System.out.println("server start at port "+ port);
            Socket socket = null;
            ExecutorService pool = Executors.newFixedThreadPool(10);
            while (true){
                socket = server.accept();
                pool.execute(new TimeServerHandler(socket));
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.2.2 弊端

  • 使用BIO读取数据时,线程会一直阻塞直到 1、有数据读 2、数据读取完毕 3、抛出异常。当客户端请求发送较慢或者网络时延较时,读取数据的线程会一直阻塞。
  • 使用BIO输出数据时,线程会一直阻塞直到  1、有数据写 2、数据写完 3、抛出异常。当服务端写数据时,如果网络情况不佳,客户端不能及时读取数据,大量数据留在TCP缓冲区,当发送端即服务端的Window size为0的时候写线程就会无法继续写从而阻塞。
  • 无论读还是写,都是阻塞的,阻塞与否以及阻塞是否严重依赖于网络传输的质量。
  • 当线程池的队列使用阻塞队列时,前台线程负责把请求封装成对象加入线程池的阻塞队列,如果网络状况十分的差,阻塞队列也满了,那么复制把请求对象加入阻塞队列的前台线程也会阻塞,整个系统失去异步性,所有的请求都会超时。

  

2.3 NIO 编程

  NIO与BIO的区别在两点

  • 面向的对象不同。BIO面向Stream,该Stream是单向通信,只能是读Stream或者写Stream。NIO面向Buffer,NIO面向buffer和channel,channel是铁路,buffer是铁路上运输的数据。
  • 阻塞性。BIO是阻塞的,NIO通过Selector实现多路复用。

2.3.1 Buffer与Channel

  

原文地址:https://www.cnblogs.com/AshOfTime/p/10806165.html