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、操作系統、數據庫、網絡等等)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章