您好,登錄后才能下訂單哦!
這篇文章主要介紹了Java阻塞的處理方式是怎樣的的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Java阻塞的處理方式是怎樣的文章都會有所收獲,下面我們一起來看看吧。
在Java1.4以前,Java的網絡編程是只有阻塞方式的,在Java1.4以及之后,Java提供了非阻塞的網絡編程API.從Java的發展來看,由于Java的快速發展,JVM性能的提升,涉足到服務端應用程序開發也越來越多,要求高性能的網絡應用越來越多,這是Java推出非阻塞網絡編程的最主要原因吧。
在阻塞的網絡編程方式中,針對于每一個單獨的網絡連接,都必須有一個線程對應的綁定該網絡連接,進行網絡字節流的處理。下面是一段代碼:
public static void main(String[] args) { try { ServerSocket ssc = new ServerSocket(23456); while (true) { System.out.println("Enter Accept:"); Socket s = ssc.accept(); try { (new Thread(new Worker(s))).start(); } catch (Exception e) { // TODO e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } } public static class Worker implements Runnable { private Socket s; private boolean running = true;; public Worker(Socket s) { this.s = s; } public void run() { try { InputStream is = s.getInputStream(); OutputStream os = s.getOutputStream(); while (running) { byte[] b = this.readByLength(is, 1024); this.process(b); } } catch (Throwable t) { // TODO t.printStackTrace(); } } private byte[] readByLength(InputStream is, int contLen) throws IOException { byte[] b = new byte[contLen]; int off = 0; int length = 0; while ((length = is.read(b, off, contLen - off)) >= 0) { off = +length; if (off >= contLen) { break; } } return b; } private void process(byte[] b) { } }
在這段代碼中,我們看到有兩個阻塞的方法,是ServerSocket的accept()方法;和InputStream的read()方式。因此我們需要兩類型的線程分別進行處理。而且每一個阻塞方法所綁定的線程的生命周期和網絡連接的生命周期是一致的。基于以上的原因,NIO應運而生,一方面,為每一個網絡連接建立一個線程對應,同時每一個線程有大量的線程處于讀寫以外的空閑狀態,因此希望降低線程的數量,降低每個空閑狀態,提高單個線程的運行執行效率,實際上是在更加充分運用CPU的計算、運行能力(因為,如果有大量的鏈路存在,就存在大量的線程,而大量的線程都阻塞在read()或者write()方法,同時CPU又需要來回頻繁的在這些線程中間調度和切換,必然帶來大量的系統調用和資源競爭.);另外一方面希望提高網絡IO和硬盤IO操作的性能。在NIO主要出現了三個新特性:
1.數據緩沖處理(ByteBuffer):由于操作系統和應用程序數據通信的原始類型是byte,也是IO數據操作的基本單元,在NIO中,每一個基本的原生類型(boolean除外)都有Buffer的實現:CharBuffer、IntBuffer、DoubleBuffer、ShortBuffer、LongBuffer、FloatBuffer和ByteBuffer,數據緩沖使得在IO操作中能夠連續的處理數據流。當前有兩種ByteBuffer,一種是Direct ByteBuffer,另外一種是NonDirect ByteBuffer;ByteBuffer是普通的Java對象,遵循Java堆中對象存在的規則;而Direct ByteBuffer是native代碼,它內存的分配不在Java的堆棧中,不受Java內存回收的影響,每一個Direct ByteBuffer都是直接分配的一塊連續的內存空間,也是NIO提高性能的重要辦法之一。另外數據緩沖有一個很重要的特點是,基于一個數據緩沖可以建立一個或者多個邏輯的視圖緩沖(View Buffer).比方說,通過View Buffer,可以將一個Byte類型的Buffer換作Int類型的緩沖;或者一個大的緩沖轉作很多小的Buffer。之所以稱為View Buffer是因為這個轉換僅僅是邏輯上,在物理上并沒有創建新的Buffer。這為我們操作Buffer帶來諸多方便。
2.異步通道(Channel):Channel是一個與操作系統緊密結合的本地代碼較多的對象。通過Channel來實現網絡編程的非阻塞操作,同時也是其與ByteBuffer、Socket有效結合充分利用非阻塞、ByteBuffer的特性的。在后面我們會看到具體的SocketChannel的用法。
3.有條件的選擇(Readiness Selection):大多數操作系統都有支持有條件選擇準備就緒IO通道的API,即能夠保證一個線程同時有效管理多個IO通道。在NIO中,由Selector(維護注冊進來的Channel和這些Channel的狀態)、SelectableChannel(能被Selector管理的Channel)和SelectionKey(SelectionKey標識Selector和SelectableChannel之間的映射關系,一旦一個Channel注冊到Selector中,就會返回一個SelectionKey對象。SelectionKey保存了兩類狀態:對應的Channel注冊了哪些操作;對應的Channel的那些操作已經準備好了,可以進行相應的數據操作了)結合來實現這個功能的。
NIO的包中主要包含了這樣幾種抽象數據類型:
◆ Buffer:包含數據且用于讀寫的線形表結構。其中還提供了一個特殊類用于內存映射文件的I/O操作。
◆ Charset:它提供Unicode字符串影射到字節序列以及逆映射的操作。
◆ Channels:包含socket,file和pipe三種管道,都是全雙工的通道。
◆ Selector:多個異步I/O操作集中到一個或多個線程中(可以被看成是Unix中select()函數的面向對象版本)。
NIO非阻塞的典型編程模型如下:
private Selector selector = null; private static final int BUF_LENGTH = 1024; public void start() throws IOException { if (selector != null) { selector = Selector.open(); } ServerSocketChannel ssc = ServerSocketChannel.open(); ServerSocket serverSocket = ssc.socket(); serverSocket.bind(new InetSocketAddress(80)); ssc.configureBlocking(false); ssc.register(selector, SelectionKey.OP_ACCEPT); try { while (true) { int nKeys = UnblockServer.this.selector.select(); if (nKeys > 0) { Iterator it = selector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey key = (SelectionKey) it.next(); if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel channel = server.accept(); if (channel == null) { continue; } channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); } if (key.isReadable()) { readDataFromSocket(key); } it.remove(); } } } } catch (IOException ioe) { ioe.printStackTrace(); } } /** * @param key * @throws IOException */ private void readDataFromSocket(SelectionKey key) throws IOException { ByteBuffer buf = ByteBuffer.allocate(BUF_LENGTH); SocketChannel sc = (SocketChannel) key.channel(); int readBytes = 0; int ret; try { while ((ret = sc.read(buf.buf())) > 0) { readBytes += ret; } } finally { buf.flip(); } // process buffer // buf.clear(); }
從這段程序,我們基本可以了解到NIO網絡編程的一些特點,創建一個SocketServer的方式已經發生了變化,需要指定非阻塞模式,需要創建一個Channel然后注冊到Selector中去,同樣,建立一個網絡連接過程也是一樣的模式,然后就是有條件的選擇(Readiness Selection).這樣,我們的每一個線程只需要處理一類型的網絡選擇。在代碼上,我們發現處理的方式和阻塞完全不一樣了,我們需要完全重新考慮如何編寫網絡通信的模塊了:
1.持久連接的超時問題(Timeout),因為API沒有直接的支持timeout的參數設置功能,因此需要我們自己實現一個這樣功能。
2.如何使用Selector,由于每一個Selector的處理能力是有限的,因此在大量鏈接和消息處理過程中,需要考慮如何使用多個Selector.
3.在非阻塞情況下,read和write都不在是阻塞的,因此需要考慮如何完整的讀取到確定的消息;如何在確保在網絡環境不是很好的情況下,一定將數據寫進IO中。
4.如何應用ByteBuffer,本身大量創建ByteBuffer就是很耗資源的;如何有效的使用ByteBuffer?同時ByteBuffer的操作需要仔細考慮,因為有position()、mark()、limit()、capacity等方法。
5.由于每一個線程在處理網絡連接的時候,面對的都是一系列的網絡連接,需要考慮如何更好的使用、調度多線程。在對消息的處理上,也需要保證一定的順序,比方說,登錄消息***到達,只有登錄消息處理之后,才有可能去處理同一個鏈路上的其他類型的消息。
6.在網絡編程中可能出現的內存泄漏問題。
在NIO的接入處理框架上,大約有兩種并發線程:
1.Selector線程,每一個Selector單獨占用一個線程,由于每一個Selector的處理能力是有限的,因此需要多個Selector并行工作。
2.對于每一條處于Ready狀態的鏈路,需要線程對于相應的消息進行處理;對于這一類型的消息,需要并發線程共同工作進行處理。在這個過程中,不斷可能需要消息的完整性;還要涉及到,每個鏈路上的消息可能有時序,因此在處理上,也可能要求相應的時序性。
當前社區的開源NIO框架實現有MINA、Grizzly、NIO framework、QuickServer、xSocket等,其中MINA和Grizzly最為活躍,而且代碼的質量也很高。他們倆在實現的方法上也完全大不一樣。(大部分Java的開源服務器都已經用NIO重寫了網絡部分。 )
關于“Java阻塞的處理方式是怎樣的”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“Java阻塞的處理方式是怎樣的”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。