Java服務器非阻塞筆記

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,這樣也避免了死鎖的存在問題纔是。

相關文章
相關標籤/搜索