NIO相關基礎篇二

匠心零度發表於2017-12-19

轉載請註明原創出處,謝謝!

上篇NIO相關基礎篇一,主要介紹了一些基本的概念以及緩衝區(Buffer)和通道(Channel),本篇繼續NIO相關話題內容,主要就是檔案鎖、以及比較關鍵的Selector,後續還會繼續有一到二篇左右與NIO內容相關。

檔案鎖(FileLock)

在看RocketMQ原始碼中,發現有關於檔案鎖的import,但是具體使用程式碼裡面註釋調了[回頭看看為什麼,理解下,到時候會在某篇文章裡進行說明](實現一個事情的方法很多,所以不一定就一種),但是為了知識的完整性,還是準備講下檔案鎖,可能以後或者那個地方可以使用,或者大家在那裡使用到都可以繼續留言討論。

檔案鎖和其他我們瞭解併發裡面的鎖很多概念類似,當多個人同時操作一個檔案的時候,只有第一個人可以進行編輯,其他要麼關閉(等第一個人操作完成之後可以操作),要麼以只讀的方式進行開啟。

在java nio中提供了新的鎖檔案功能,當一個執行緒將檔案鎖定之後,其他執行緒無法操作此檔案,檔案的鎖操作是使用FileLock類來進行完成的,此類物件需要依賴FileChannel進行例項化。

檔案鎖方式

  • 共享鎖:允許多個執行緒進行檔案讀取。
  • 獨佔鎖:只允許一個執行緒進行檔案的讀寫操作。

備註:檔案鎖定以整個 Java 虛擬機器來保持。但它們不適用於控制同一虛擬機器內多個執行緒對檔案的訪問。 多個併發執行緒可安全地使用檔案鎖定物件。

Java檔案依賴FileChannel的主要涉及如下4個方法:

方法 說明
lock() 獲取對此通道的檔案的獨佔鎖定。
lock(long position, long size, boolean shared) 獲取此通道的檔案給定區域上的鎖定。
tryLock() throws IOException 試圖獲取對此通道的檔案的獨佔鎖定。
tryLock(long position, long size, boolean shared) throws IOException 試圖獲取對此通道的檔案給定區域的鎖定。
lock()等同於lock(0L, Long.MAX_VALUE, false)
tryLock()等同於tryLock(0L, Long.MAX_VALUE, false)

lock()

tryLock()

lock()和tryLock()的區別

  • lock()阻塞的方法,鎖定範圍可以隨著檔案的增大而增加。無參lock()預設為獨佔鎖;有參lock(0L, Long.MAX_VALUE, true)為共享鎖。
  • tryLock()非阻塞,當未獲得鎖時,返回null。無參tryLock()預設為獨佔鎖;有參tryLock(0L, Long.MAX_VALUE, true)為共享鎖。

簡單例項程式碼:

File file = new File("d:" + File.separator + "test.txt") ;
FileOutputStream output = null ;
FileChannel fout = null ;
try {
    output = new FileOutputStream(file,true) ;
    fout = output.getChannel() ;// 得到通道
    FileLock lock = fout.tryLock() ; // 進行獨佔鎖的操作
    if(lock!=null){
	System.out.println(file.getName() + "檔案鎖定") ;
	Thread.sleep(5000) ;
	lock.release() ;	// 釋放
	System.out.println(file.getName() + "檔案解除鎖定。") ;
    }
} catch (IOException e) {
    e.printStackTrace();
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    if(fout!=null){
	try {
	    fout.close();
	} catch (IOException e) {
	    e.printStackTrace();
	}
    }
    if(output!=null){
	try {
	    output.close();
	} catch (IOException e) {
	    e.printStackTrace();
	}
    }
}
複製程式碼

執行結果:

NIO相關基礎篇二

NIO相關基礎篇二

**說明:**目前也很少接觸到關於檔案鎖的靈活運用,RocketMQ那塊程式碼給註釋了,如果那我瞭解,歡迎留言討論。

Selector

說明:

  • FileChannel是可讀可寫的Channel,它必須阻塞,不能用在非阻塞模式中
  • SocketChannel與FileChannel不同:新的Socket Channel能在非阻塞模式下執行並且是可選擇的。不再需要為每個socket連線指派執行緒了。使用新的NIO類,一個或多個執行緒能管理成百上千個活動的socket連線,使用Selector物件可以選擇可用的Socket Channel。

以前的Socket程式是阻塞的,伺服器必須始終等待客戶端的連線,而NIO可以通過Selector完成非阻塞操作。

備註:其實NIO主要的功能是解決服務端的通訊效能。 上篇NIO相關基礎篇一的知識,馬上這塊也是需要使用到的。

Selector一些主要方法:

方法 說明
open() 開啟一個選擇器。
select() 選擇一組鍵,其相應的通道已為 I/O 操作準備就緒。
selectedKeys() 返回此選擇器的已選擇鍵集。

SelectionKey的四個重要常量:

欄位 說明
OP_ACCEPT 用於套接字接受操作的操作集位。
OP_CONNECT 用於套接字連線操作的操作集位。
OP_READ 用於讀取操作的操作集位。
OP_WRITE 用於寫入操作的操作集位。

**說明:**其實四個常量就是Selector監聽SocketChannel四種不同型別的事件。

如果你對不止一種事件感興趣,那麼可以用"位或"操作符將常量連線起來,如下: int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

#NIO 簡單例項程式碼 服務端程式碼:

int port = 8000;
// 通過open()方法找到Selector
Selector selector = Selector.open();
// 開啟伺服器的通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 伺服器配置為非阻塞
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
// 進行服務的繫結
serverSocket.bind(address);
// 註冊到selector,等待連線
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("伺服器執行,埠:" + 8000);

// 資料緩衝區
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

while (true) {
if ((selector.select()) > 0) { // 選擇一組鍵,並且相應的通道已經準備就緒
	Set<SelectionKey> selectedKeys = selector.selectedKeys();// 取出全部生成的key
	Iterator<SelectionKey> iter = selectedKeys.iterator();
	while (iter.hasNext()) {
		SelectionKey key = iter.next(); // 取出每一個key
		if (key.isAcceptable()) {
			ServerSocketChannel server = (ServerSocketChannel) key.channel();
			// 接收新連線 和BIO寫法類是都是accept
			SocketChannel client = server.accept();
			// 配置為非阻塞
			client.configureBlocking(false);
			byteBuffer.clear();
			// 向緩衝區中設定內容
			byteBuffer.put(("當前的時間為:" + new Date()).getBytes());
			byteBuffer.flip();
			// 輸出內容
			client.write(byteBuffer);
			client.register(selector, SelectionKey.OP_READ);
		} else if (key.isReadable() && key.isValid()) {
			SocketChannel client = (SocketChannel) key.channel();
			byteBuffer.clear();
			// 讀取內容到緩衝區中
			int readSize = client.read(byteBuffer);
			if (readSize > 0) {
				System.out.println("伺服器端接受客戶端資料:" + new String(byteBuffer.array(), 0, readSize));
				client.register(selector, SelectionKey.OP_WRITE);
			}
		} else if (key.isWritable() && key.isValid()) {
			SocketChannel client = (SocketChannel) key.channel();
			byteBuffer.clear();
			// 向緩衝區中設定內容
			byteBuffer.put(("歡迎關注匠心零度,已經收到您的反饋,會第一時間回覆您。感謝支援!!!").getBytes());
			byteBuffer.flip();
			// 輸出內容
			client.write(byteBuffer);
			client.register(selector, SelectionKey.OP_READ);
		}
	}
	selectedKeys.clear(); // 清楚全部的key
}
}
複製程式碼

客戶端程式碼:

// 開啟socket通道
SocketChannel socketChannel = SocketChannel.open();
// 設定為非阻塞方式
socketChannel.configureBlocking(false);
// 通過open()方法找到Selector
Selector selector = Selector.open();
// 註冊連線服務端socket動作
socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 連線
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000));

/* 資料緩衝區 */
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

while (true) {
if ((selector.select()) > 0) { // 選擇一組鍵,並且相應的通道已經準備就緒
	Set<SelectionKey> selectedKeys = selector.selectedKeys();// 取出全部生成的key
	Iterator<SelectionKey> iter = selectedKeys.iterator();
	while (iter.hasNext()) {
		SelectionKey key = iter.next(); // 取出每一個key
		if (key.isConnectable()) {
			SocketChannel client = (SocketChannel) key.channel();
			if (client.isConnectionPending()) {
				client.finishConnect();
				byteBuffer.clear();
				// 向緩衝區中設定內容
				byteBuffer.put(("isConnect,當前的時間為:" + new Date()).getBytes());
				byteBuffer.flip();
				// 輸出內容
				client.write(byteBuffer);
			}
			client.register(selector, SelectionKey.OP_READ);
		} else if (key.isReadable() && key.isValid()) {
			SocketChannel client = (SocketChannel) key.channel();
			byteBuffer.clear();
			// 讀取內容到緩衝區中
			int readSize = client.read(byteBuffer);
			if (readSize > 0) {
				System.out.println("客戶端接受伺服器端資料:" + new String(byteBuffer.array(), 0, readSize));
				client.register(selector, SelectionKey.OP_WRITE);
			}
		} else if (key.isWritable() && key.isValid()) {
			SocketChannel client = (SocketChannel) key.channel();
			byteBuffer.clear();
			// 向緩衝區中設定內容
			byteBuffer.put(("nio文章學習很多!").getBytes());
			byteBuffer.flip();
			// 輸出內容
			client.write(byteBuffer);
			client.register(selector, SelectionKey.OP_READ);
		}
	}
	selectedKeys.clear(); // 清楚全部的key
}
}
複製程式碼

程式執行結果截圖:

NIO相關基礎篇二

NIO相關基礎篇二

**說明:**上面僅僅是一個demo,其實使用nio開發複雜度很高的,需要考慮:鏈路的有效性校驗機制(安全認證、半包和粘包等)、鏈路的斷連重連機制(網路閃斷重連)、可靠性設計(心跳檢測,訊息重發、黑白名單)以及可擴充套件性的考慮等等,這些都是很複雜,那裡搞不好就容易出錯,看netty 權威指南的時候 記得作者他們那個時候還沒有netty,經常出現一些莫名問題需要進行解決,而很多問題netty已經幫我們解決了,所以有必要好好看看netty了(目前作者也在看netty權威指南,唯一不爽的時候,裡面大量程式碼,習慣用工具檢視程式碼(編輯器檢視程式碼變色,可以跳轉等),求netty權威指南程式碼地址,看書裡面程式碼特別變扭!,謝謝)

簡單聊幾句AIO

雖然NIO在網路操作中提供了非阻塞方法,但是NIO的IO行為還是同步的,對於NIO來說,我們的業務執行緒是在IO操作準備好時,才得到通知,接著就有這個執行緒自行完成IO操作,但是IO操作的本身其實還是同步的。

AIO是非同步IO的縮寫,相對與NIO來說又進了一步,它不是在IO準備好時再通知執行緒,而是在IO操作完成後在通知執行緒,所以AIO是完全不阻塞的,我們的業務邏輯看起來就像一個回撥函式了。

**備註:**AIO就是簡單提提,NIO還沒有搞明白,後續還有一到二篇左右與NIO內容相關,主要談談一些select、poll、epoll、零拷貝等一些內容,如果有關零拷貝比較好的資料,歡迎在留言區進行留言,讓零度也學習下,系統的瞭解下,謝謝!!!

如果讀完覺得有收穫的話,歡迎點贊、關注、加公眾號【匠心零度】。


個人公眾號,歡迎關注,查閱更多精彩歷史!!!

匠心零度公眾號

相關文章