在java线程中,有Socket的IO操作,线程由suspended变成了stepping

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在java线程中,有Socket的IO操作,线程由suspended变成了stepping相关的知识,希望对你有一定的参考价值。

我先发送消息,然后接收消息,但是当代码执行到消息读时,线程由suspended变成了stepping

Socket s = new Socket(ip, port);
OutputStream os = s.getOutputStream();
os.write(msg);
os.flush();

InputStream is = s.getInputStream();
byte[] memory = new byte[300];
int memoryLength = 0;

// 执行到这个while线程就变成stepping了
while ((memoryLength = is.read(memory )) != -1)
// 业务处理

参考技术A 您好,提问者:
线程应该分开,OutputStream 是一个线程,InputStream 是一个线程,不能混用。追问

为什么呢?服务器的读了消息会有响应,如果我只读一次,而不是用循环,就不会变成这样。现在我的需求就是给服务发消息,然后它返回,我根据返回信息判断处理业务信息。如果弄成两个线程,就相当于异步了。。

追答

不会异步的吧,ServerSocket的accept本身是多任务,怎么会异步?
如果你担心这个可以使用wait等待 notify唤醒。

追问

我说的是我的Socket,不是ServerSocket,我要向ServerSocket发信息,然后根据ServerSocket返回信息处理业务。我想把Socket做成同步的,不想弄两个线程,搞了两天了都。。

追答

看来你是不明白原理啊,唉,本来还想给你讲讲原理的。那你用匿名内部类线程。例如:
class Thread()
public void run()
xxxx; //这里先判断是接受还是发送,判断完了做相应的处理即可。

.start();

追问

我做的是web项目。有些业务需求是需要和其他系统交互的,我们用socket进行通信,其他系统是socket的服务端。web中的某些action需要将信息发到socket的服务端,服务端处理信息,然后返回给action操作结果,然后action根据返回的操作结果处理业务。我们的业务需求是这样啊。

追答

要你这么说的话,请求过来的肯定有相应的判断方式,完全不用线程吧。。而且好几个系统都不冲突的,因为Socket本身就有多线程。

从操作系统内核看Java非阻塞IO事件检测

非阻塞服务器模型最重要的一个特点是,在调用读取或写入接口后立即返回,而不会进入阻塞状态。在探讨单线程非阻塞IO模型前必须要先了解非阻塞情况下Socket事件的检测机制,因为对于非阻塞模式最重要的事情是检测哪些连接有感兴趣的事件发生,一般会有如下三种检测方式。

应用程序遍历socket检测

如图所示,当多个客户端向服务器请求时,服务器端会保存一个socket连接列表,应用层线程对socket列表进行轮询尝试读取或写入。对于读取操作,如果成功读取到若干数据则对读取到的数据进行处理,读取失败则下个循环再继续尝试;对于写入操作,先尝试将数据写入指定的某个socket,写入失败则下个循环再继续尝试。

技术分享

这样看来,不管多少个socket连接都可以被一条线程管理起来,一条线程负责遍历这些socket列表,不断地尝试读取或写入数据,很好地利用了阻塞的时间,处理能力得到提升。但这种模型需要在应用程序中遍历所有的socket列表,同时需要处理数据的拼接,连接空闲时可能也会占用较多CPU资源,不适合实际使用。对此改进是使用事件驱动的非阻塞方式。

内核遍历socket的事件检测

这种方式将socket的遍历工作交给了操作系统内核,对socket遍历的结果组织成一系列的事件列表并返回应用层处理。对于应用层,他们需要处理的对象就是这些事件,这就是其中一种事件驱动的非阻塞方式的实现。

如图所示,服务器端有多个客户端连接,应用层向内核请求读写事件列表。内核遍历所有socket并生成对应的可读列表readList和可写列表writeList,readList标明了每个socket是否可读,例如socket1的值为1,表示可读,socket2的值为0,表示不可读。writeList则标明了每个socket是否可写。应用层遍历读写事件列表readList和writeList,做相应的读写操作。

技术分享

内核遍历socket的方式已经不用在应用层对所有socket进行遍历,将遍历工作下移到内核层,这种方式有助于提高检测效率。但是它需要将所有连接的可读事件列表和可写事件列表传到应用层,假如socket连接数量大起来的话,列表从内核复制到应用层也是不小的开销。另外,当活跃连接较少时,内核与应用层之间存在很多无效的数据拷贝,因为它是将活跃和不活跃的连接状态都拷贝到应用层了。

内核基于回调的事件检测

通过遍历的方式检测socket是否可读可写是一种效率比较低的方式,不管是在应用层遍历还是在内核遍历。所以需要另外一种机制来优化遍历的方式,那就是回调函数。内核中socket都对应一个回调函数,当客户端往socket发送数据时,内核从网卡接收数据后就会调回调函数将此socket作为可读事件加入到事件列表中。应用层获取此事件列表即可得到所有感兴趣的事件。

如图所示,服务器端有多个客户端socket连接,首先应用层告诉内核每个socket感兴趣的事件;接着当客户端发送数据过来时,对应会有一个回调函数,内核从网卡复制数据成功后即调回调函数将socket1作为可读事件event1加入到事件列表,同样地,内核发现网卡可写时就将socket2作为可写事件event2添加到事件列表中;最后,应用层向内核请求读写事件列表,内核将包含了event1和event2的事件列表返回应用层,应用层通过遍历事件列表得知socket1有数据待读取,于是进行读操作,而socket2则可以写入数据。

技术分享

这种方式由操作系统内核维护客户端的所有连接并通过回调函数不断更新可读可写事件列表,而应用层线程只要遍历这些事件列表即可知道可读取或可写入的连接,进而对这些连接进行读写操作。极大提高了检测效率,自然处理能力也更强。

上面介绍了三种非阻塞的事件检测机制,对于Java来说,非阻塞IO的实现完全是基于操作系统内核的非阻塞IO,它将操作系统的非阻塞IO的差异屏蔽了并提供统一的API,让我们不必关心操作系统。JDK会帮我们选择非阻塞IO的实现方式,例如对于Linux系统,在支持epoll的情况下JDK会优先选择用epoll实现Java的非阻塞IO。这种非阻塞方式的事件检测机制就是效率最高的“内核基于回调的事件检测”。

以上是关于在java线程中,有Socket的IO操作,线程由suspended变成了stepping的主要内容,如果未能解决你的问题,请参考以下文章

java NIO

java NIO

java NIO

java 多次new DataOutputStream而不关闭,线程阻塞

JAVA基础笔记(很实用)继承-多态-抽象类-接口-异常-集合-IO-线程-Socket

java socket服务器端线程卡死