网络I/O模型--02阻塞模式(多线程)
当服务器收到客户端 X 的请求后(读取到所有请求数据后),将这个请求送入一个独立线程进行处理,然后主线程继续接收客户端 Y 的请求。
客户端一侧也可以使用一个子线程和服务器端进行通信。这样客户端主线程的其他工作就不受影响了,当服务器端有响应信息时再由这个子线程通过监听模式/观察模式或者类似的其他设计模式通知主线程。
服务端
package testBlockSocket; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // 通过多线程的方式处理socket的连接 public class SocketServer2MiltiThread { private final static Logger LOGGER = LoggerFactory.getLogger(SocketServer2MiltiThread.class); public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(8888); try { while (true) { Socket socket = serverSocket.accept(); // 业务处理过程可以交给一个线程,不过线程的创建很耗资源 // 最终改变不了, accept () 只能在被阻塞的情况一个一个接收 Socket SocketServerThread socketServerThread = new SocketServerThread(socket); Thread thread = new Thread(socketServerThread); thread.start(); } } catch (Exception e) { SocketServer2MiltiThread.LOGGER.error(e.getMessage(), e); } finally { if (serverSocket != null) { serverSocket.close(); // 当然 , 接收到客户端的 Socket 后 , 业务的处理过程可以交给一个线程来做 } } } } class SocketServerThread implements Runnable { private final static Logger LOGGER = LoggerFactory.getLogger(SocketServerThread.class); private Socket socket; public SocketServerThread(Socket socket) { this.socket = socket; } @Override public void run() { InputStream inputStream = null; OutputStream outputStream = null; try { // 下面我们收取信息 inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); Integer sourcePort = socket.getPort(); int maxLen = 1024; byte[] contextBytes = new byte[maxLen]; // 同样无法改变 read 方法被阻塞,直到操作系统有数据准备好的现象 int realLen = inputStream.read(contextBytes, 0, maxLen); String message = new String(contextBytes, 0, realLen); LOGGER.info("服务器收到来自于端曰 :" + sourcePort + " 的信息:" + message); // 下面开始发送响应信息 outputStream.write("因发响应信息 !".getBytes()); } catch (Exception e) { SocketServerThread.LOGGER.error(e.getMessage(), e); } finally { // 试图关闭连接 try { inputStream.close(); outputStream.close(); socket.close(); } catch (Exception e) { SocketServerThread.LOGGER.error(e.getMessage(), e); } } } }
1)虽然在服务器端,接收到数据后的处理交给了 一个独立线程进行,但是操作系 统通知accept()的方式还是单个线程运行的 。 也就是说,实际上是服务器接收到数据报文后的“业务处理过程”可以应用多线程技术,但是数据报文的接收还是需要一个接一个地来,从以上的示例代码和其调试过程我们都可以明确看到这一点。
2)在 Linux 系统中,可以创建的线程是有限的。可以通过 cat /proc/sys/kernel/threads-max命令查看可以创建的最大线程数。当然这个值可以更改,但是参与调度的线程数量越大, CPU 用在线程间切换所需的时间也就越长,用来处理真正业务的资源也就越少。CPU 线程状态间切换的性能消耗是非常巨大的,后文我们会对这个描述给出相应的实例以便将这个感性的认识具体化,帮助读者真正认识线程间状态切换的性能代价 。
3)创建一个线程是有较大的资源消耗的 。 例如 NM 创建一个线程时,即使这个线程不做任何工作,JVM都会分配一个独立线程技空间(不同 JDK 版本默认的大小不一样〉 。虽然它可以通过“-Xss ”参数进行大小调整,但这不影响 CPU 一级、 二次缓存中的数据出现线程数据的换入/换出 。
总结:
阻塞模型的问题关键不在于是否使用了多线程(包括线程池)处理并发请求,而在于 accept()、 read()的操作点都被阻塞了 。
完