Java的阻塞与异步模型

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/barlay/article/details/83348051

From Blog:Java——BIO、NIO、AIO
线程会阻塞在什么地方?调用读取文件的API从硬盘上读取文件时,通过网络访问API从网络上请求资源时,等待获取到需要的数据以后再进行后续的处理。那么我们应该怎样在线程阻塞的时候让这个线程去干别的事呢,然后等数据来了以后再进行后续的数据处理。传统的编程模型肯定是不可能的,因为你不能让线程主动去执行别的代码,而且你怎么知道你需要的数据准备好了,如果数据准备好了,应该能够通知线程进行后续的处理。如果按照常规的想法,要实现异步执行,需要线程能被主动调度,而且还要有事件通知机制确定线程被调度的时机。
NIO和传统IO(一下简称IO)之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。从这句话上可以看出,NIO多少还是采用了生产者消费者模式,生产者生产数据,消费者消费数据,哪个生产者生产出数据,就会有消费者消费掉,它们之间采用缓冲区来进行耦合。这种模式其实就是异步模式的一种。 参见博客:Java NIO?看这一篇就够了!
传统的IO模型是一个线程处理一个请求,NIO可以一个线程同时处理多个请求,当然,一个线程是不能同时处理多个请求的,但是多个请求并不需要同时处理,每次只处理准备好的请求,没有准备好的请求暂不处理,这样就需要一个轮询机制来从多个请求当中选出准备好的请求来处理。
NIO示意图
详见博客:深入理解Java-NIO
可以看到,NIO并不是为了解决单线程阻塞的问题,而是把所有阻塞的操作都交由一个线程处理,可以看到,我们并没有去调度线程,而是去调度通道(Channel),因为我们并不能主动去调度线程执行某个任务,只能采用这种方法。通道是用来调度的,保存了Channel当前的状态,并不能保存数据,所以我们需要一个容器来接受通过Chennel获取到的数据,这个容器我们就叫做Buffer,也被称作缓冲区。可以看到,所有的Channel都可以公用一个缓存区,因为同一时间只有一个Channel得到处理(单线程模型)。
From Blog:java中的AIO
可以看到,上面的过程需要我们手动去轮询准备好的Channel,我们其实只需要关注Channel准备好以后获取到读取的数据以后我们需要干什么,我们其实关注的只是获取到的数据。我们期望的其实是类似于JS中的回调的那种写法。jdk7中新增了一些与文件(网络)I/O相关的一些api。这些API被称为NIO.2,或称为AIO(Asynchronous I/O)。具体的写法跟JS中的回调非常相似,代码如下:

  final AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel
            .open()
            .bind(new InetSocketAddress("0.0.0.0",8888));
    channel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
        @Override
        public void completed(final AsynchronousSocketChannel client, Void attachment) {
            channel.accept(null, this);
 
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                @Override
                public void completed(Integer result_num, ByteBuffer attachment) {
                    attachment.flip();
                    CharBuffer charBuffer = CharBuffer.allocate(1024);
                    CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
                    decoder.decode(attachment,charBuffer,false);
                    charBuffer.flip();
                    String data = new String(charBuffer.array(),0, charBuffer.limit());
                    System.out.println("read data:" + data);
                    try{
                        client.close();
                    }catch (Exception e){}
                }
 
                @Override
                public void failed(Throwable exc, ByteBuffer attachment) {
                    System.out.println("read error");
                }
            });
        }
 
        @Override
        public void failed(Throwable exc, Void attachment) {
            System.out.println("accept error");
        }
    });
 
    while (true){
        Thread.sleep(1000);
    }

再附上一篇博文:Java NIO:浅析I/O模型
但是,在JS中我们知道,回调会造成Callback Hell(回调地狱),那么这个在JS中是怎么解决的呢?
参见以下博客,说的很详细:
ES6之async+await同步/异步方案详解
自学-ES6篇-异步操作和Async函数
js-ES6学习笔记-async函数
还有一种单线程并发的模型,叫做协成,在GO语言中用的比较多:
Golang教程:(二十一)协程
还有设计模式:Python 中最简最好的设计模式

猜你喜欢

转载自blog.csdn.net/barlay/article/details/83348051