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,这样也避免了死锁的存在问题才是。

相关文章
相关标签/搜索