• Java服务器非阻塞笔记


    Java在做服务器的时候,我们司空见惯的就是阻塞式的方式,也就是每当发生一次连接,就new 一个Thread出来,假如线程在读写,连接发生问题,线程则会一直阻塞,但是并不会消亡。所以随着线程数的增加,CPU的利用率会随之降低,因此我们应当采用非阻塞式的方式,能更好的解决问题。

    看了一本书《Java网络编程精解》最近才大致的通读了一遍,这本书上面讲解的很详细,简单的将前面的非阻塞与阻塞的结合部分,代码做了一定的修改。并自己进行了一些抽象,测试通过没有提。

    用一个AcceptThread采用阻塞的方式,来处理连接。

    用一个RWThread采用非阻塞的方式,来处理读写问题。

    这样既充分利用了多核心CPU的特性,两个线程做两个事情,也解决了开始提到的问题。

    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事件

    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()方法,这个方法是阻塞的,如果不发生连接此方法将一直阻塞下去。

    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的使用。

    后来想到的:

    这样的效率并不是最好的,因为我们每一次it.next(),我们都会讲Selector当中注册时事件全部遍历下来,从而才会获取到相应的事件,并作出处理。因此将读写的Selector与接收的Accept事件分开,可能会好一些。也就是Accept当中注册读写事件的时候,将事件注册到一个新的Selector当中。单独遍历不同的Selector,这样也避免了死锁的存在问题才是。

  • 相关阅读:
    neo4j︱与python结合的py2neo使用教程
    Neo4j 简介 2019
    110.Java对象的序列化
    109.Java序列流
    108.Java装饰器设计模式
    107.Java中IO流_字符流的缓冲区
    106.Java中IO流_字符流的异常处理
    105.Java中IO流_字符流拷贝文件
    104.Java中IO流_字符流_Writer
    103.Java中IO流_字符流_Reader
  • 原文地址:https://www.cnblogs.com/flashbird/p/3338626.html
Copyright © 2020-2023  润新知