服务端线程模型-线程池服务模型
单线程服务器
初学网络编程时,我们写的服务端的代码大部分如下所示。
在一个循环中等待客户端请求,一旦接到请求就在当前线程与客户端进行通信,这就是单线程服务模型。
这种模型有个问题,就是当请求量一上来,同时第二步的操作耗时过长时,许多请求就会阻塞在系统的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网络编程精解》(孙卫琴)