1 概述
之前讲过客户端的并发访问量增加时,服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终进程宕机或者僵死,从而不能对外提供服务。
所以我们采用一个位异步I/O的通信框架,采用线程池和任务队列实现,当客户端接入时,将客户端的Socket封装成一个Task(改任务实现Java.lang.Runnable线程任务接口)交给后端的线程池进行处理,JDK的线程池维护一个消息队列和N个活跃的线程,对消息队列中Socket任务进行处理,由于线程池可以设置消息队列的大小和最大线程数。因此,他的资源是可控的,无论多少个客户端进行访问,都不会导致资源的耗尽和宕机。
2 代码展示
客户端代码:
public class Client {
public static void main(String[] args) {
try {
//请求与服务端的socket连接
Socket socket = new Socket("127.0.0.1", 8888);
//得到一个打印流
PrintStream printStream = new PrintStream(socket.getOutputStream());
Scanner sc = new Scanner(System.in);
while (true){
System.out.print("请说:");
String msg = sc.nextLine();
printStream.println(msg);
printStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端代码:
public class Server {
public static void main(String[] args) {
try {
System.out.println("*********进入服务端**********");
//注册端口
ServerSocket ss = new ServerSocket(8888);
//定义一个循环接收客户端的Socket连接请求
//初始化一个线程池对象
HandleSocketServletPool pool = new HandleSocketServletPool(6, 10);
while (true){
Socket socket = ss.accept();
//将socket对象包装成一个线程池进行处理
//将socket封装成一个任务对象
Runnable target = new ServerRunnableTarget(socket);
pool.excute(target);
}
}catch (Exception e){
}
}
}
自定义线程池:
public class HandleSocketServletPool {
/**
* 1 创建一个线程池的成员变量用于存储一个线程的对象
*/
private ExecutorService executorService;
/**
* public ThreadPoolExecutor(int corePoolSize,核心线程数量
* int maximumPoolSize,最大线程数量
* long keepAliveTime,空闲时间
* TimeUnit unit,
* BlockingQueue<Runnable> workQueue)
* 2 创建这个类的对象的时候需要初始化线程池对象
*/
public HandleSocketServletPool(int maxThreadNum,int queueSize){
executorService = new ThreadPoolExecutor(3, maxThreadNum, 120,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize));
}
/**
* 3 提供一个方法来提交任务给线程池任务队列来暂存,等着线程池来处理
*/
public void excute(Runnable target){
executorService.execute(target);
}
}
线程任务处理:
public class ServerRunnableTarget implements Runnable{
private Socket socket;
public ServerRunnableTarget(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
//从socket管道中得到一个字节输入流
InputStream is = socket.getInputStream();
//使用缓冲字符输入流包装字节输入流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = bufferedReader.readLine()) != null){
System.out.println(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在idea上模拟四个客户端请求:
但是服务端只接收到了3个:
因为我们自定义的线程池只支持3个线程,所以我们的第四个线程进入了等待队列,但如果我们关掉了其中一个线程,第四个客户端的请求就会被请求到。
我们可以看到当我们关掉其中一个客户端,就会处理第四个客户端请求。
小结:
1 伪异步io采用了线程池实现,因此避免了为每一个请求创建一个独立线程造成线程资源耗尽都无问题,但由于底层依然是采用同步阻塞模型,因此无法从根本上解决问题
2 如果单个消息处理的缓慢,或者服务器线程池的全部线程都被阻塞,那么后续的socket的I/O消息都将在对列中排队。新的socket请求将会被拒绝,客户端产生大量的连接超时。