netty权威指南学习笔记一——NIO入门(2)伪异步IO
在上一节我们介绍了四种IO相关编程的各个特点,并通过代码进行复习了传统的网络编程代码,伪异步主要是引用了线程池,对BIO中服务端进行了相应的改造优化,线程池的引入,使得我们在应对大量客户端请求的时候不会导致虚拟机可贵的线程资源耗尽而宕机,在本小节,我们来看看伪异步代码。多注意引入的线程池代码。
伪异步IO模型图如下():
当有新的客户端请求到来时,将socket封装成一个Task(该任务实现了Runnable接口),投递到线程池中进行处理。而线程池始终维护一个消息队列和N个活跃着的线程,这些线程被分配,对消息队列中的Task进行处理。由于线程池可以设置消息队列的大小和线程的数量,因此不论多少个客户端请求都不会导致资源耗尽和宕机。
下面附上刚改造完成的代码:为了便于理解,在代码的最后本博主添加上关于线程池运行的原理的基本知识,以便于理解伪I/O的代码。
改造后的服务端代码:
1 package com.example.biodemo; 2 3 4 import java.io.*; 5 import java.net.ServerSocket; 6 import java.net.Socket; 7 8 public class TimeServer { 9 public static void main(String[] args) throws IOException { 10 int port = 8090; 11 if (args != null && args.length > 0) { 12 try { 13 port = Integer.valueOf(args[0]); 14 } catch (NumberFormatException e) { 15 port = 8090; 16 } 17 } 18 ServerSocket server = null; 19 try { 20 server = new ServerSocket(port); 21 System.out.println("the timeServer is start in port :" + port); 22 Socket socket = null; 23 // 引入线程池start 24 TimeServerHandlerExecutePool singleExecutor = new TimeServerHandlerExecutePool(50,10000); 25 while (true) { 26 socket = server.accept(); 27 // 替换BIO中new Thread(new TimeServerHandler(socket)).start();为下一行代码 28 singleExecutor.execute(new TimeServerHandler(socket)); 29 // 引入线程池end 30 31 } 32 } finally { 33 if (server != null) { 34 System.out.println("the time server close"); 35 server.close(); 36 server = null; 37 } 38 } 39 40 } 41 }
服务端引用的线程池,自定义的线程池代码
1 package com.example.biodemo; 2 3 import java.util.concurrent.ArrayBlockingQueue; 4 import java.util.concurrent.ExecutorService; 5 import java.util.concurrent.ThreadPoolExecutor; 6 import java.util.concurrent.TimeUnit; 7 8 //自定义的线程池 9 public class TimeServerHandlerExecutePool { 10 private ExecutorService executor; 11 // 线程池构造函数,创建线程池 12 public TimeServerHandlerExecutePool(int maxPoolSize,int queueSize){ 13 // new ThreadPoolExecutor(线程池基本大小,线程池最大数量,存活时间,时间单位,消息队列) 14 executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),maxPoolSize,120L, 15 TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(queueSize)); 16 17 } 18 // 该方法会不断从消息队列中获取任务执行 19 public void execute(java.lang.Runnable task){ 20 executor.execute(task); 21 } 22 23 }
下面是和上一小节一样的 TimeServerHandler和客户端代码
package com.example.biodemo; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class TimeServerHandler implements Runnable { private Socket socket; public TimeServerHandler(Socket socket) { this.socket = socket; } @Override public void run() { BufferedReader in = null; PrintWriter out = null; try { in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out = new PrintWriter(this.socket.getOutputStream(), true); String currentTime = null; String body = null; while (true) { body = in.readLine(); if (body == null) { break; } System.out.println("the time server receive order:" + body); currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString() : "bad order"; out.println(currentTime); } } catch (Exception e) { if (in != null) { try { in.close(); } catch (IOException el) { el.printStackTrace(); } } if (out != null) { out.close(); out = null; } if (this.socket != null) { try { this.socket.close(); } catch (IOException e1) { e1.printStackTrace(); } this.socket = null; } } } }
客户端代码
1 package com.example.biodemo; 2 3 import java.io.*; 4 import java.net.Socket; 5 6 public class TimeClient { 7 public static void main(String[] args) { 8 int port = 8090; 9 if (args != null && args.length > 0) { 10 try { 11 port = Integer.valueOf(args[0]); 12 } catch (NumberFormatException ne) { 13 port = 8090; 14 } 15 } 16 Socket socket = null; 17 BufferedReader in = null; 18 PrintWriter out = null; 19 try { 20 socket = new Socket("127.0.0.1", port); 21 System.out.println(socket.getInputStream()); 22 in = new BufferedReader(new InputStreamReader(socket.getInputStream())); 23 out = new PrintWriter(socket.getOutputStream(), true); 24 out.println("QUERY TIME ORDER"); 25 System.out.println("send order 2 server succeed."); 26 String resp = in.readLine(); 27 System.out.println("now is :" + resp); 28 } catch (IOException e1) { 29 30 } finally { 31 if (out != null) { 32 out.close(); 33 out = null; 34 } 35 36 if (in != null) { 37 try { 38 in.close(); 39 } catch (IOException e2) { 40 e2.printStackTrace(); 41 } 42 in = null; 43 if (socket != null) { 44 try { 45 socket.close(); 46 } catch (IOException e3) { 47 e3.printStackTrace(); 48 } 49 50 } 51 socket = null; 52 } 53 54 55 } 56 } 57 }
其运行与小节一结果一致。
下面为了便于理解添加的线程池相关的代码部分,附上一些线程池运行的原理等相关知识。
当提交一个新任务到线程池时,线程池的处理流程如上图所示,具体为:
1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
上面这个图是ThreadPoolExecutor 执行 executor的示意图。ThreadPoolExecutor 执行 executor方法分为下面4种情况:
1、如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
2、如果运行的线程等于或者多于corePoolSize,则任务添加到BlockingQueue。
3、如果无法将任务加入到BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。
4、如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。
如上,相信大家能够更好的理解伪异步I/O与BIO的不同了。