Java在作服務器的時候,咱們司空見慣的就是阻塞式的方式,也就是每當發生一次鏈接,就new 一個Thread出來,假如線程在讀寫,鏈接發生問題,線程則會一直阻塞,可是並不會消亡。因此隨着線程數的增長,CPU的利用率會隨之下降,所以咱們應當採用非阻塞式的方式,能更好的解決問題。java
看了一本書《Java網絡編程精解》最近才大體的通讀了一遍,這本書上面講解的很詳細,簡單的將前面的非阻塞與阻塞的結合部分,代碼作了必定的修改。並本身進行了一些抽象,測試經過沒有提。編程
用一個AcceptThread採用阻塞的方式,來處理鏈接。服務器
用一個RWThread採用非阻塞的方式,來處理讀寫問題。網絡
這樣既充分利用了多核心CPU的特性,兩個線程作兩個事情,也解決了開始提到的問題。socket
public class Application { //存在死鎖風險,再認真考慮一下,雖然機率低一些。讀寫和接收線程問題!!!! public static void main(String[] args){ Server server = Server.getInstance(); //啓動接收線程 new AcceptThread(server).start(); //啓動讀寫線程 new RWThread(server).start(); } }
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; //使用單例模式,建立惟一的Selector public class Server { private static Server server; //選擇器 public Selector selector; //建立serverSocket public ServerSocketChannel serverSocket; //端口號 private int port = 8086; private Server(){ try { //初始化Selector selector = Selector.open(); //初始化serverSocket serverSocket = ServerSocketChannel.open(); serverSocket.configureBlocking(false); //能夠綁定到相同端口 serverSocket.socket().setReuseAddress(true); //綁定到某個地址上 serverSocket.socket().bind(new InetSocketAddress(port)); serverSocket.register(selector, SelectionKey.OP_ACCEPT); } catch (IOException e) { e.printStackTrace(); } } public static Server getInstance(){ if(server == null){ server = new Server(); } return server; } }
咱們使用單例模式,建立ServerSocketChannel,獲取惟一的Selector,由於Selector當中會將ServerSocketChannel,SocketChannel的key存儲在Selector當中。(注:正由於兩個線程分別一個接受要往Selector當中添加,另外一個讀寫線程要從Selector當中獲取SocketChannel,因此要防止死鎖狀況的出現)與此同時,咱們還給ServerSocket註冊了Accept事件ide
import java.io.IOException; 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; public class AcceptThread extends Thread{ private Selector selector; public AcceptThread(Server server){ selector = server.selector; } public void run() { try { while(true){ selector.select(); //阻塞 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while(it.hasNext()){ SelectionKey keys = it.next(); //接收狀態下的操做 if(keys.isAcceptable()){ //獲取當前連接的SocketChannel ServerSocketChannel ssc = (ServerSocketChannel)keys.channel(); SocketChannel sc = ssc.accept(); if(sc!=null){ //非阻塞 sc.configureBlocking(false); synchronized(selector){ System.out.println("鏈接成功"); sc.register(selector,SelectionKey.OP_READ|SelectionKey.OP_WRITE); } } } } } } catch (IOException e) { e.printStackTrace(); } } }
咱們經過便利Selector對象來獲取Accept事件狀態,從而獲取SocketChannel,而且註冊Write/Read事件咱們會將SocketChannel註冊到Selector當中,所以在註冊事件的同時,咱們要加鎖。測試
剛開始,咱們採用的是select()方法,這個方法是阻塞的,若是不發生鏈接此方法將一直阻塞下去。spa
import java.io.IOException; 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; public class RWThread extends Thread { //key集合類 private Selector selector; //BufferByte private ByteBuffer byteBuffer; public RWThread(Server server){ selector = server.selector; } @Override public void run() { while(true){ synchronized(selector){ try{ if(selector.selectNow()>0){ //非阻塞 System.out.println(selector.selectedKeys().size()); Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while(it.hasNext()){ SelectionKey keys = it.next(); //可讀狀態下的操做 if(keys.isReadable()){ } //可寫狀態下的操做 if(keys.isWritable()){ } try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } catch (IOException e) { e.printStackTrace(); } } } } }
此次讀寫selectNow()是非阻塞的,因此將會一直執行下去,咱們爲了避免讓CPU運行過高,所以加入了一個睡眠線程,從而下降CPU的使用。.net
後來想到的:線程
這樣的效率並非最好的,由於咱們每一次it.next(),咱們都會講Selector當中註冊時事件所有遍歷下來,從而纔會獲取到相應的事件,並做出處理。所以將讀寫的Selector與接收的Accept事件分開,可能會好一些。也就是Accept當中註冊讀寫事件的時候,將事件註冊到一個新的Selector當中。單獨遍歷不一樣的Selector,這樣也避免了死鎖的存在問題纔是。