ByteBuffer
----------------
1.介紹
字節緩衝區,內部封裝的是數組。
[屬性]
a)capacity
容量,緩衝區的總大小。java
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 ,
//緩衝區歸位。app
buf.flip() //拍板,limit = position , position = 0 ; mark = -1
//dom
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方法提交就忘了,不能實現同步執行的。