doclist 阅读(19) 评论(0)

单线程服务器

初学网络编程时,我们写的服务端的代码大部分如下所示。

在一个循环中等待客户端请求,一旦接到请求就在当前线程与客户端进行通信,这就是单线程服务模型。

这种模型有个问题,就是当请求量一上来,同时第二步的操作耗时过长时,许多请求就会阻塞在系统的Socket队列中,无法及时得到处理,响应时间增加,严重会导致系统拒接请求(Socket队列溢出),直接影响用户体验。

private void service() {

    while (true) {

        // 1.接到一个客户端请求
        Socket socket = serverSocket.accept();

        // 2.从socket中获取输入输出流,与客户端进行通信
        InputStream in = socket.getInputStream();
        OutputStream out = socket.getOutputStream();
    }
}

 

 

多线程服务模型

为了应对单线程服务器的缺陷,自然而然就是每来一个请求都开一个线程去处理,伪代码如下。这个模型能同时处理多个请求,每来一个请求就开一个线程去处理,对每个客户都给予快速的响应不阻塞。但是不足之处也很明显:

每来一个请求就创建一个线程,如果请求量大的话,创建和销毁的线程就会非常多,服务器创建和销毁线程的开销将巨增;

频繁的创建和销毁线程,会导致频繁的上下文切换,从而影响服务器性能;

每一个线程都是会占用内存资源的,大量请求意味着大量的内存要拿去开辟这种线程,可能会导致系统的内存空间不足;

private void service() {

    while (true) {

        // 1.接到一个客户端请求
        Socket socket = serverSocket.accept();

        // 2.开启一个线程去处理请求
        new Thread(new ServiceTask(socket)).start();
    }
}

 

 

 

线程池服务模型

单纯的多线程服务模型有个问题就是开的线程太多,严重影响系统性,但是不开的话,请求处理又不及时。

有没有办法可以既不用开太多线程,又能保证请求被及时处理呢?

这时可以在服务端设置一个请求队列,同时开适当数量的服务线程,这些服务线程不断的从请求队列中拿请求去进行处理,这个模型的核心思想就是通过复用线程,从而减少创建线程和销毁线程带来的系统开销,这就是线程池服务模型(Master-Worker模式)。 

private void service() {

    // 1.创建一个线程池
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    while (true) {

        // 1.接到一个客户端请求
        Socket socket = serverSocket.accept();

        // 2.将请求方式请求队列中,等待服务线程处理
        executorService.execute(new ServiceTask(socket));
    }
}
 
 

 

使用线程池服务模型应该注意的点

线程太多:如果线程数开的太多,这些线程会消耗包括内存在内的其他系统资源,影响系统性能。所以应当根据具体情况开合适的线程数

请求太多:请求太多意味着请求队列过大,同样也会占用过多的系统资源。所以应当设置适当的拒绝策略,保护系统的同时不过与影响用户体验

线程泄漏:如果服务线程阻塞了,比如等待用户请求,比如死锁。这个服务线程就无法去处理请求,线程池将会失去这个“劳动力”,如果接二连三所有的线程都因为某个原因而无法处理请求,线程池终将没有剩余的工作线程去处理队列中的请求,这就是线程泄漏,所以,应当在阻塞的地方设置合理的最大阻塞时间避免永久的阻塞,同时避免死锁。

引用

1.《Java网络编程精解》(孙卫琴)