NIO 之 Channel

可參考之前寫過的文章:NIO 之 Channel實現原理

概述

通道( Channel)是 java.nio 的主要創新點。它們既不是一個擴展也不是一項增強,而是全新、極好的 Java I/O 示例,提供與 I/O 服務的直接連接。 Channel 用於在字節緩衝區和位於通道另一側的實體(通常是一個文件或套接字)之間有效地傳輸數據。

Channel 接口定義

public interface Channel extends Closeable {
    public boolean isOpen();
    public void close() throws IOException;
}

Channel 接口,只抽象了 isOpen() 方法和 close() 方法。

是否感覺很奇怪,爲什麼沒有 open() 方法?

Channel 概述

I/O 分爲File I/O 和 Stream I/O。
File I/O 對應的是文件(file)通道。
Stream I/O 對應的是( socket)通道。

FileChannel 類和三個 socket 通道類: SocketChannel、 ServerSocketChannel 和 DatagramChannel。

通道可以以多種方式創建。 Socket 通道有可以直接創建新 socket 通道的工廠方法。但是一個FileChannel 對象卻只能通過在一個打開的 RandomAccessFile、 FileInputStream 或 FileOutputStream對象上調用 getChannel( )方法來獲取。您不能直接創建一個 FileChannel 對象。(這也是 Channel 接口沒有定義 open() 方法的原因)。

ByteChannel

通過源碼發現每一個 file 或 socket 通道都實現ByteChannel。

ByteChannel

public interface ByteChannel extends ReadableByteChannel, WritableByteChannel { }

ReadableByteChannel

public interface ReadableByteChannel extends Channel {
    public int read(ByteBuffer dst) throws IOException;
}

WritableByteChannel

public interface WritableByteChannel extends Channel{
    public int write(ByteBuffer src) throws IOException;
}

通道可以是單向或者雙向的。實現這兩種接口其中之一的類都是單向的,只能在一個方向上傳輸數據。如果一個類同時實現這兩個接口,那麼它是雙向的,可以雙向傳輸數據。

每一個 file 或 socket 通道都實現全部三個接口。從類定義的角度而言,這意味着全部 file 和 socket 通道對象都是雙向的。這對於 sockets 不是問題,因爲它們一直都是雙向的,不過對於 files 卻是個問題了。

一個文件可以在不同的時候以不同的權限打開。從 FileInputStream 對象的getChannel( )方法獲取的 FileChannel 對象是隻讀的,不過從接口聲明的角度來看卻是雙向的,因爲
FileChannel 實現 ByteChannel 接口。在這樣一個通道上調用 write( )方法將拋出未經檢查的NonWritableChannelException 異常,因爲 FileInputStream 對象總是以 read-only 的權限打開文件。一個連接到只讀文件的 Channel 實例不能進行寫操作,即使該實例所屬的類可能有 write( )方法。基於此,程序員需要知道通道是如何打開的,避免試圖嘗試一個底層 I/O服務不允許的操作。

阻塞非阻塞

通道可以以阻塞( blocking)或非阻塞( nonblocking)模式運行。非阻塞模式的通道永遠不會讓調用的線程休眠。請求的操作要麼立即完成,要麼返回一個結果表明未進行任何操作。只有面向流的( stream-oriented)的通道,如 sockets 和 pipes 才能使用非阻塞模式。file 通道是不能以非阻塞的模式運行。

Channel.close()

與緩衝區(Buffer)不同,通道(Channel)不能被重複使用。一個打開的通道即代表與一個特定 I/O 服務的特定連接並封裝該連接的狀態。當通道關閉時,那個連接會丟失,然後通道將不再連接任何東西。

調用通道的close( )方法時,可能會導致在通道關閉底層I/O服務的過程中線程暫時阻塞,哪怕該通道處於非阻塞模式。通道關閉時的阻塞行爲(如果有的話)是高度取決於操作系統或者文件系統的。

源碼簡略如下:

    //該代碼是 FileChannel 的關閉方法 (在FileChannel 的父類 AbstractInterruptibleChannel 中)
    public final void close() throws IOException {
        synchronized (closeLock) {
            if (!open)
                return;
            open = false;
            implCloseChannel();
        }
    }

從上面代碼中可以分析出在一個通道上多次調用close( )方法是沒有壞處的,但是如果第一個線程在close( )方法中阻塞(使用synchronized 鎖),那麼在它完成關閉通道之前,任何其他調用close( )方法都會阻塞。後續在該已關閉的通道上調用close( )不會產生任何操作,只會立即返回。

Channel.isOpen( )

可以通過 isOpen( )方法來測試通道的開放狀態。如果返回 true 值,那麼該通道可以使用。如果返回 false 值,那麼該通道已關閉,不能再被使用。嘗試進行任何需要通道處於開放狀態作爲前提的操作,如讀、寫等都會導致 ClosedChannelException 異常。

源碼簡略如下:

//該代碼在FileChannel 的子類中實現的
public class FileChannelImpl  extends FileChannel{

    public int read(ByteBuffer dst) throws IOException {
        ensureOpen();
        ......
    }

    public int write(ByteBuffer src) throws IOException {
        ensureOpen();
        ......
    }
    private void ensureOpen() throws IOException {
        if (!isOpen())
            throw new ClosedChannelException();
    }
}

通道響應 Interrupt 中斷

通道引入了一些與關閉和中斷有關的新行爲。通道實現 InterruptibleChannel 接口。
如果一個線程在一個通道上被阻塞並且同時被中斷(由調用該被阻塞線程的 interrupt( )方法的另一個線程中斷),那麼該通道將被關閉,該被阻塞線程也會產生一個 ClosedByInterruptException 異常。

源碼簡略如下:

public class FileChannelImpl extends FileChannel{

    public int write(ByteBuffer src) throws IOException {
       ......
       end(n > 0);
       ......
    }

    public int read(ByteBuffer dst) throws IOException {
       ......
       end(n > 0);
       ......
    }

    public FileLock lock(long position, long size, boolean shared)  throws IOException {
       ......
       end(n > 0);
       ......
    }
    .......
}
public abstract class AbstractInterruptibleChannel
    implements Channel, InterruptibleChannel {

    protected final void end(boolean completed)
        throws AsynchronousCloseException
    {
        blockedOn(null);
        Thread interrupted = this.interrupted;
        if (interrupted != null && interrupted == Thread.currentThread()) {
            interrupted = null;
            throw new ClosedByInterruptException();
        }
        if (!completed && !open)
            throw new AsynchronousCloseException();
    }
    ......
}

Interrupt 關閉 Channel 實例

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class ChannelInterruptDemo {

    public static void main(String[] args) throws Exception  {
        FileChannel fc = new RandomAccessFile(new File("d:/a.txt"), "rw").getChannel();
        System.out.println("Channel isOpen : " + fc.isOpen());

        Thread t = new Thread(new Task(fc));
        t.start();
        t.interrupt();
        t.join();

        System.out.println("Channel isOpen : " + fc.isOpen());

        fc.close();
    }
}
class Task implements Runnable{
    FileChannel fc;

    public Task(FileChannel fc) {
        this.fc = fc;
    }

    @Override
    public void run() {
        try {
            fc.position(Integer.MAX_VALUE/2);
            fc.write(ByteBuffer.wrap("hello".getBytes()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

運行結果:輸出結果
從結果中發現,只要Channel 所在的線程 interrupt 就會自動關閉channel。


想了解更多精彩內容請關注我的公衆號

本人簡書blog地址:http://www.jianshu.com/u/1f0067e24ff8    
點擊這裏快速進入簡書
GIT地址:http://git.oschina.net/brucekankan/
點擊這裏快速進入GIT

發佈了132 篇原創文章 · 獲贊 79 · 訪問量 40萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章