jdk源码之java的NIO稍微了解篇

从JDK1.5开始,java引入了java.nio包,nio的含义为非阻塞型IO。这一篇就从使用到源码来简单了解一下NIO吧。

一、基本用法

 

		RandomAccessFile aFile = null;
        try {
            aFile = new RandomAccessFile(NIOTest.class.getClassLoader().getResource("nio.txt").getPath(), "rw");
            FileChannel fileChannel = aFile.getChannel();
            ByteBuffer buf = ByteBuffer.allocate(1024);
            int bytesRead = fileChannel.read(buf);
            System.out.println(bytesRead);
            while (bytesRead != -1) {
                buf.flip();
                while (buf.hasRemaining()) {
                    System.out.print((char) buf.get());
                }
                buf.compact();
                bytesRead = fileChannel.read(buf);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (aFile != null) {
                    aFile.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

如上例程,读取一个resource下的nio.txt文件,获取其对应Channel,定义一个ByteBuffer,然后调用read方法。

这里的Channel与传统阻塞型IO中的Stream类似,不同在于,Stream的读写是流的形式的,也就是一个个字节读的,而Channel却是直接传入一个Buffer块,然后就然后那样嗯。

所以我们就可以得出NIO和传统IO的两大区别:1.面向块而非面向流;2.顾名思义,可以非阻塞。

二、非阻塞的好处

既然NIO是非阻塞的,那么非阻塞IO到底有什么好处呢?

既然要思考非阻塞的好处,我们自然要看看阻塞通常被用在哪。最典型的例子就是Socket,而我们这里就使用非阻塞的Socket:SocketChannel来展示下非阻塞IO的作用:

	Selector selector = null;
        ServerSocketChannel ssc = null;
        try {
            selector = Selector.open();
            ssc = ServerSocketChannel.open();
            ssc.socket().bind(new InetSocketAddress(PORT));
            ssc.configureBlocking(false);
            ssc.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                if (selector.select(TIMEOUT) == 0) {
                    System.out.println("==");
                    continue;
                }
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                while (iter.hasNext()) {
                    SelectionKey key = iter.next();
                    if (key.isAcceptable()) {
                        handleAccept(key);
                    }
                    if (key.isReadable()) {
                        handleRead(key);
                    }
                    if (key.isConnectable()) {
                        System.out.println("isConnectable = true");
                    }
                    iter.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (selector != null) {
                    selector.close();
                }
                if (ssc != null) {
                    ssc.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

这是一个Socket服务端的实现,利用非阻塞服务端ServerSocketChannel和选择器Selector完美展示如何在单一的线程中监听多个客户端的请求。

传统的IO型ServerSocket通常只在一个线程中完成accept,此后的read则是为Socket单独开启一个线程,通过阻塞的方式去监听。而这里,accept、read是平级的,他们都在主线程中完成,而在read操作中,可以从key中获取具体的SocketChannel:

    public static void handleRead(SelectionKey key) throws IOException {
        SocketChannel sc = (SocketChannel) key.channel();
        ByteBuffer buf = (ByteBuffer) key.attachment();
        long bytesRead = sc.read(buf);
        while (bytesRead > 0) {
            buf.flip();
            while (buf.hasRemaining()) {
                System.out.print((char) buf.get());
            }
            System.out.println();
            buf.clear();
            bytesRead = sc.read(buf);
        }
        if (bytesRead == -1) {
            sc.close();
        }
    }

一个线程,可以完成多个客户端的IO读取,究其原因自然是因为其非阻塞的特性,他虽然也会不断地轮询IO,但是并不因为没有读到数据而阻塞IO端口,而是返回一个null,因此他可以在一个线程中同时监听多个IO端,这给降低并发压力带来可能。而许多框架(如Netty)就是这么去实现的。

三、实现源码

粗略阅读发现,NIO的源码实现相当复杂,方便起见,这里只粗略看看部分的源码,只说明一下其中注意到的细节,暂时不整个流程地去解读源码了。

3.1 ServerSocketChannel的启动

    ServerSocket socket;
	
	ServerSocketChannelImpl(SelectorProvider var1) throws IOException {
        super(var1);
        this.fd = Net.serverSocket(true);
        this.fdVal = IOUtil.fdVal(this.fd);
        this.state = 0;
    }

	public ServerSocket socket() {
        Object var1 = this.stateLock;
        synchronized(this.stateLock) {
            if (this.socket == null) {
                this.socket = ServerSocketAdaptor.create(this);
            }

            return this.socket;
        }
    }

从源码不难发现,ServerSocketChannel的启动最根本的还是使用了ServerSocket。

3.2 ServerSocketChannel和Selector的绑定

 

    public final SelectionKey register(Selector sel, int ops,
                                       Object att)
        throws ClosedChannelException
    {
        synchronized (regLock) {
            if (!isOpen())
                throw new ClosedChannelException();
            if ((ops & ~validOps()) != 0)
                throw new IllegalArgumentException();
            if (blocking)
                throw new IllegalBlockingModeException();
            SelectionKey k = findKey(sel);
            if (k != null) {
                k.interestOps(ops);
                k.attach(att);
            }
            if (k == null) {
                // New registration
                synchronized (keyLock) {
                    if (!isOpen())
                        throw new ClosedChannelException();
                    k = ((AbstractSelector)sel).register(this, ops, att);
                    addKey(k);
                }
            }
            return k;
        }
    }

从代码中可以看出,register的过程其实是一个双向绑定的过程。

    protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) {
        if (!(var1 instanceof SelChImpl)) {
            throw new IllegalSelectorException();
        } else {
            SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this);
            var4.attach(var3);
            Set var5 = this.publicKeys;
            synchronized(this.publicKeys) {
                this.implRegister(var4);
            }

            var4.interestOps(var2);
            return var4;
        }
    }

selector的register将ServerSocketChannel传给了SelectionKey的实现类,然后调用implRegister,这个方法因操作系统而异,在windows中:

    protected void implRegister(SelectionKeyImpl var1) {
        Object var2 = this.closeLock;
        synchronized(this.closeLock) {
            if (this.pollWrapper == null) {
                throw new ClosedSelectorException();
            } else {
                this.growIfNeeded();
                this.channelArray[this.totalChannels] = var1;
                var1.setIndex(this.totalChannels);
                this.fdMap.put(var1);
                this.keys.add(var1);
                this.pollWrapper.addEntry(this.totalChannels, var1);
                ++this.totalChannels;
            }
        }
    }

可以看到这里把var1传给了一个Wrapper。最终这玩意调用了一个native的方法,具体就不多说了。

3.3 Selector的select

依然看windows下的实现。

    protected int doSelect(long var1) throws IOException {
        if (this.channelArray == null) {
            throw new ClosedSelectorException();
        } else {
            this.timeout = var1;
            this.processDeregisterQueue();
            if (this.interruptTriggered) {
                this.resetWakeupSocket();
                return 0;
            } else {
                this.adjustThreadsCount();
                this.finishLock.reset();
                this.startLock.startThreads();

                try {
                    this.begin();

                    try {
                        this.subSelector.poll();
                    } catch (IOException var7) {
                        this.finishLock.setException(var7);
                    }

                    if (this.threads.size() > 0) {
                        this.finishLock.waitForHelperThreads();
                    }
                } finally {
                    this.end();
                }

                this.finishLock.checkForException();
                this.processDeregisterQueue();
                int var3 = this.updateSelectedKeys();
                this.resetWakeupSocket();
                return var3;
            }
        }
    }

	private int updateSelectedKeys() {
        ++this.updateCount;
        byte var1 = 0;
        int var4 = var1 + this.subSelector.processSelectedKeys(this.updateCount);

        WindowsSelectorImpl.SelectThread var3;
        for(Iterator var2 = this.threads.iterator(); var2.hasNext(); var4 += var3.subSelector.processSelectedKeys(this.updateCount)) {
            var3 = (WindowsSelectorImpl.SelectThread)var2.next();
        }

        return var4;
    }

从这里可以看出,它通过遍历选择器拥有的线程,然后将其转变为SelectThread,调用其subSelector.processSelectedKeys()方法,最终完成了select的操作。

总结

对于NIO的使用,网上的教程非常多,而且也有Netty这样的框架使用它,原本想要从源码角度去对NIO进行解读,但是博客写的并不如意。事实上NIO本身就是操作系统支持的东西,并非单纯JAVA层面的东西,java.nio包中大部分都是接口,具体的实现类都在sun.nio包中,其中的实现代码也是非常的晦涩难懂,变量的命名几乎和代码混淆了一般全是var1var2的命名,但是其基本原理也是不难看出来。所谓Selector,其实无非是把ServerSocketChannel绑定在内部,内部又拥有用于轮训的Thread,可以切换式地将IO放入Thread中读取,其最终调用的也是底层的操作系统开放的接口。

这篇博客写的不如意,NIO的源码比先前想象的复杂多,以后可能会继续写NIO的博客来完善,这篇姑且做个伏笔。

此外最近要找工作,源码的解读先告一段落,接下来可能会根据面试复习的内容去写一写读书笔记(JVM、操作系统、数据库、网络等等)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值