可參考之前寫過的文章: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