ByteBuffer
----------------
1.介紹
字節緩沖區,內部封裝的是數組。
[屬性]
a)capacity
容量,緩沖區的總大小。
b)position
位置,當前指針的位置。數組的下標值。
c)limit
限制,對緩沖區使用的限制,前n個可以使用的元素個數,
也可以理解為第一個不能使用的元素下標值,默認是容量。
d)mark
對當前的指針位置進行標記,方便后來進行reset重置指針。
e)remain
剩余的空間,limit - position.
f)原則
0 <= mark <= position <= limit <= capacity
2.方法
buf.limit() //get
buf.limit(int n) //set
buf.position() //get
buf.position(int n) //set
buf.mark() //當前位置,
buf.remaining() //limit - position
buf.hasRemaining() //判斷是否還有可用空間
buf.clear() //清空,pos = 0 , limit = capacity , mark = -1 ,
//緩沖區歸位。
buf.flip() //拍板,limit = position , position = 0 ; mark = -1
//
NIO ------------ 1、傳統IO 傳統IO是阻塞模式,處理並發的時候,需要啟動多個線程, cpu需要在多線程上下文之間進行頻繁切換,而大多數線程通 常處於阻塞狀態,導致CPU資源利用底下。 2、New IO 非阻塞,傳統IO是阻塞模式,不需要啟動大量線程,通常結合 線程池能夠實現高並發編程。 3、零拷貝 常規拷貝文件需要經過四次數據交換,分別是 (1)從disk到系統空間, (2)從系統空間到用戶空間, (3)用戶空間到系統空間, (4)系統空間到目標設備的緩沖區。 零拷貝是將磁盤文件復制系統內核,從系統內核直接輸出數據到 目標設備緩沖區。 java的FileChannel.transfer()可以實現零拷貝。 零拷貝有個2G文件大小限制。 5、虛擬內存 FileChannel.map()方法可以將磁盤文件的特定區域映射到內存, 直接操縱內存緩沖區時,速度更快,而且最終數據會按照自己的 設定同步到磁盤,這個磁盤寫入過程不需要我們處理。 映射模式有三種: a、read-only 對緩沖區只能讀,寫入出異常 b、read-write 能讀能寫,最終緩沖區數據會進入到文件中。 c、private 可以寫入數據到緩沖區,但不能傳播到磁盤,並且對其程序不可見。 該模式通話用於從磁盤文件加載數據到內存完成初始化工作,但不 將緩沖區數據寫回到磁盤文件中。 e、編程實現 import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; /** * 使用NIO實現虛擬內存 */ public class TestVirtualMemory { public static void main(String[] args) throws Exception { RandomAccessFile raf = new RandomAccessFile("d:/1.txt" , "rw") ; FileChannel fc = raf.getChannel() ; MappedByteBuffer buf = fc.map(FileChannel.MapMode.READ_WRITE , 1 , 9) ; // buf.put(4 , (byte)'Y') ; char c = (char)buf.get(1); System.out.println(c); for(int i = 0 ; i < 9 ; i ++){ buf.put(i ,(byte)(97 + i)) ; } fc.close(); } } 工作模式 ---------------- 1、單工 消息只能向一方傳遞。 2、雙工 消息可以雙向傳遞。 半雙工 :同一時刻只能向一方傳輸。 全雙工 : 同一時刻可以向兩方傳輸。 Socket下NIO編程 ---------------- 1、介紹 編程思路和傳統的socket編程大致相同,但引進了新的概念就是Channel。 ServerSocket對應的ServerSocketChannel,Socket對應SocketChannel。 先開啟服務器通道,讓后綁定到特定的端口上,在開啟client端通道,並 將客戶端通道連接到服務器通道。 java的NIO的socket編程可以工作在阻塞和非阻塞模式下,默認是阻塞的。 通過ServerSocketChannel.configureBlocking(false) 配置成非阻塞。 2、ServerSocketChannel工作流程 a、開啟ServerSocketChannel b、設置阻塞模式 c、綁定到特定端口 d、開啟挑選器 e、在挑選器中注冊服務器通道,需要指定感興趣事件(accept) 感興趣事件有四中 1、op_accept 2、op_connect 對應的方法isConnectable(),在客戶端判斷該方法,並 通過finishConnect()方法來完成連接。 3、op_read 4、op_write f、挑選器循環挑選 g、取得挑選器內部的挑選集合 h、處理每個key 3、SocketChannel.finishConnect()方法 客戶端socket封裝的channel,其有finishConnect()方法, 該方法完成通道的連接過程。 非阻塞連接通過設置通道為非阻塞模式以及調用connect()方法來 完成初始化。一旦連接建立或連接嘗試失敗,socketchannel都會變 成connectable狀態,就可以調用該方法完成連接序列。如果連接操作 失敗,該方法就會拋出相應異常。 如果連接已經建立,該方法不阻塞,而是立刻返回true。如果通道工作在 非阻塞模式下而連接過程尚未完成,則該方法返回false。如果通道工作在 阻塞模式下,該方法便會阻塞直到完成或失敗,最終要么返回true,要么拋 出異常。 調用讀寫時等待連接完成。 注意,客戶端套接字通道調用該方法, 4、selector中的內部集合 內部維護了三個集合: 1、key set 包含了注冊的所有通道對應的key,keys()返回所有注冊的key。 selector.keys() 2、selected-key set 挑選出來的key的集合,在自身已經發生了至少一個感興趣事件通道 所對應的key,selectedKeys()方法返回該集合,該集合是(1)的子集。 select.selectedKeys() 3、cancelled-key key被撤銷但所對應通道尚未注銷的key集合,該集合無法直接訪問,也是 key set的子集。 select.cancel(key); 3、編程實現 [服務器端] package com.oldboy.java.nio; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; /** * 服務器端 */ public class MyServer { public static void main(String[] args) throws Exception { //開啟服務器通道 ServerSocketChannel ssc = ServerSocketChannel.open(); //配置非阻塞 ssc.configureBlocking(false) ; //創建地址對象 InetSocketAddress addr = new InetSocketAddress( 8888) ; //綁定通道到特定的地址上 ssc.bind(addr) ; //開啟一個挑選器 Selector sel = Selector.open() ; //在挑選中注冊channel ssc.register(sel , SelectionKey.OP_ACCEPT) ; System.out.println("注冊服務器通道完成!!!"); //創建字節緩沖區 ByteBuffer buf = ByteBuffer.allocate(1024) ; //開始循環挑選 while(true){ // System.out.println("開始挑選...."); sel.select() ; // System.out.println("挑出發生了!!!"); //得到挑選出來的key集合 Iterator<SelectionKey> it = sel.selectedKeys().iterator() ; while(it.hasNext()){ //一定是服務器通道 SelectionKey key = it.next() ; if(key.isAcceptable()){ //接受新socket SocketChannel sc0 = ssc.accept(); //配置非阻塞模式 sc0.configureBlocking(false) ; //在selector中注冊socketChannel,指定感興趣事件 sc0.register(sel, SelectionKey.OP_READ | SelectionKey.OP_CONNECT) ; } //可連接 if(key.isConnectable()){ SocketChannel sc0 = (SocketChannel) key.channel(); //完成連接 sc0.finishConnect() ; } //是否可讀 if(key.isReadable()){ SocketChannel sc0 = (SocketChannel) key.channel(); //內存輸出流 ByteArrayOutputStream baos = new ByteArrayOutputStream() ; //讀到了數據 while(sc0.read(buf) > 0){ // buf.flip(); byte[] arr = buf.array(); baos.write(arr , 0 , buf.limit()); buf.clear() ; } // String msg = new String(baos.toByteArray()); InetSocketAddress remoteAddr = (InetSocketAddress) sc0.socket().getRemoteSocketAddress(); String ip = remoteAddr.getAddress().getHostAddress(); int port = remoteAddr.getPort() ; System.out.printf("[%s:%d]說 : %s\r\n" , ip , port , msg); } //刪除該key it.remove(); } } } } [客戶端] package com.oldboy.java.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; /** * */ public class MyClient { public static void main(String[] args) throws Exception { SocketChannel sc = SocketChannel.open(); InetSocketAddress addr = new InetSocketAddress("localhost", 8888); sc.connect(addr); while (true) { ByteBuffer buf = ByteBuffer.wrap("hello world".getBytes()); sc.write(buf); Thread.sleep(1000); } } } 線程池 ------------------------------- 1、簡介 池化設計模式,通過容器維護固定數量的線程,使用 固定線程數實現任務的執行。 2、Executors 創建線程池的工廠類,提供很多工具方法。 常用的Executors.newFixedThreadPool(3); 執行任務時,將任務提交到任務隊列,在將來的某個 時刻調度執行。 3、ThreadPoolExecutor 線程池執行器,優勢一是提升異步執行大量任務的性能,線程池維護 了基本統計信息,比如完成的線程數。 線程池有corePoolSize和maximumPoolSize. 如果線程數少於該值,創建新線程執行任務。 如果線程數大於core數但小於最大數,只有隊列滿時創建新線程。 意味着在隊列沒有滿的時候,如果線程數超過核心數,則盡量使用 現有線程執行這些任務。 如果核心數據量和最大數量相同,則表示固定大小的線程池。 如果最大值無界,則可以適應任意數量的並發度。 4.AtomicInteger 原子整數,內部封裝了一個整數,確保對該整數的修改是原子性。 //控制信號 ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 控制信號控制的是池的狀態,內部包含兩個成分, workerCount //線程數,最大值是2^29 - 1 , runState //狀態 控制信號的前三位是狀態信息,后面的29位是線程數信息。 000 | 00000 0000 0000 0000 0000 0000 0000 狀態控制如下: [RUNNING] 接受新任務,處理隊列中的任務。 -1 << COUNT_BITS; 111 0..0 [SHUTDOWN] 不再接受新任務,但處理隊列中任務。 000 0..0 [STOP] 停止態,不接收新任務,不處理隊列任務,終止執行的任務。 馬上整頓。 001 0..0 [TIDYING] 所有任務都結束了,線程數歸0,轉換到該狀態的線程調用terminated()方法。 010 0..0 [TERMINATED] terminated()方法執行結束。 011 0..0 5、同步執行問題 線程池的工作原理是異步執行,執行任務時,僅僅是放入任務隊列,將來的某個時刻 來執行任務,如果想要同步執行, public class App { public static void main(String[] args) { //創建任務對象 Runnable task = new Runnable() { public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " : " + "hello world"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } ; //新建固定線程池 ExecutorService pool = Executors.newFixedThreadPool(3); for(int i = 0 ; i < 6 ; i ++){ //執行任務 Future f = pool.submit(task , 100); //submit 和返回值調用的f.get實現同步執行 try { Object obj = f.get(); System.out.println(obj); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } } 5.總結 ThreadPoolExecutor內部有兩個集合,一個是Workers集合,一個是WorkQueue任務 隊列,Worker內部關聯一個Thread和Runnable對象。線程池執行任務時,要么添加 新worker,要么添加任務到隊列。如果添加新worker,直接啟動worker關聯的線程。 新線程調用worker的runWorker()方法,該方法從隊列中剪切任務來執行的。 線程池的submit方法可以實現同步執行,因為有返回值Future對象,該對象包含執行 結果,在沒有計算完成前,無法得到該結果,只能等待。可以通過調用pool.submit(task , 100) 來直接指定結果。execute方法提交就忘了,不能實現同步執行的。