4.1 非阻塞 vs 阻塞
阻塞
- 阻塞模式下,相关方法都会导致线程暂停
- ServerSocketChannel.accept 会在没有连接建立时让线程暂停
- SocketChannel.read 会在没有数据可读时让线程暂停
- 阻塞的表现其实就是线程暂停了,暂停期间不会占用 cpu,但线程相当于闲置
- 单线程下,阻塞方法之间相互影响,几乎不能正常工作,需要多线程支持
- 但多线程下,有新的问题,体现在以下方面
- 32 位 jvm 一个线程 320k,64 位 jvm 一个线程 1024k,如果连接数过多,必然导致 OOM,并且线程太多,反而会因为频繁上下文切换导致性能降低
- 可以采用线程池技术来减少线程数和线程上下文切换,但治标不治本,如果有很多连接建立,但长时间 inactive,会阻塞线程池中所有线程,因此不适合长连接,只适合短连接
服务器端
// 使用 nio 来理解阻塞模式, 单线程 // 0. ByteBuffer ByteBuffer buffer = ByteBuffer.allocate(16); // 1. 创建了服务器 ServerSocketChannel ssc = ServerSocketChannel.open();
// 2. 绑定监听端口 ssc.bind(new InetSocketAddress(8080));
// 3. 连接集合 List channels = new ArrayList<>(); while (true) { // 4. accept 建立与客户端连接, SocketChannel 用来与客户端之间通信 log.debug("connecting..."); SocketChannel sc = ssc.accept(); // 阻塞方法,线程停止运行 log.debug("connected... {}", sc); channels.add(sc); for (SocketChannel channel : channels) { // 5. 接收客户端发送的数据 log.debug("before read... {}", channel); channel.read(buffer); // 阻塞方法,线程停止运行 buffer.flip(); debugRead(buffer); buffer.clear(); log.debug("after read...{}", channel); } }
客户端
SocketChannel sc = SocketChannel.open(); sc.connect(new InetSocketAddress("localhost", 8080)); System.out.println("waiting...");