这两天做项目遇到奇怪的BUG,使用JDK socket编程进行服务器和客户端进行通信,读取输入输出流使用了ObjectInputStreamObjectOutputStream,结果两边都卡住没反应,疑似客户端出现了点问题,后面发现在服务端的getInputStream()的一行代码处也产生了阻塞,当时的写法:
//服务端
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); //在此处阻塞
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
//客户端
ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); //在此处阻塞
ObjectOutputStream out = new ObjectOutputStream((socket.getOutputStream()));

第一遍解决是通过调换服务端的输入输出流两行代码的位置,可以解决

后面发现只要调换客户端的输入输出流两行代码的位置,无论服务端两行代码的位置怎么换都可以。

//客户端修改如下
ObjectOutputStream out = new ObjectOutputStream((socket.getOutputStream()));
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());

初步猜想是,当在客户端使用错误的方式(先打开输入流再打开输出流),客户端会在输入流这里产生阻塞,而服务端由于还没有收到客户端的请求,等待客户端发送请求,也在socket.getInputStream()处产生了阻塞而无法处理消息。结果就是客户端也一直阻塞在socket.getInputStram()方法上,两边都发生了阻塞。

查阅资料发现可能是ObjectOutputStreamObjectInputStream的问题,使用原生字节流(InputStreamOutputStream)做了实验,发现确实与原生字节流代码顺序无关,那么大概率就是ObjectOutputStreamObjectInputStream有关。在stackoverflow上看到有一篇问答,也提到了ObjectOutputStreamObjectInputStream的问题getInputStream blocks?

yes, this question has been asked many times before. the object stream format has a header, and the ObjectInputStream reads that header on construction. therefore, it is blocking until it receives that header over the socket. assuming your client/server code looks similar, after you construct the ObjectOutputStream, flush it. this will force the header data over the wire.

ObjectOutputStreamObjectInputStream加上测试之后发现确实与客户端new出ObjectOutputStreamObjectInputStream的顺序有关,先output再input则正常使用。

最后stackoverflow上找到解答 java - new ObjectInputStream() blocks - Stack Overflow

You need to create the ObjectOutputStream before the ObjectInputStream at both sides of the connection(!). When the ObjectInputStream is created, it tries to read the object stream header from the InputStream. So if the ObjectOutputStream on the other side hasn't been created yet there is no object stream header to read, and it will block indefinitely.

Or phrased differently: If both sides first construct the ObjectInputStream, both will block trying to read the object stream header, which won't be written until the ObjectOutputStream has been created (on the other side of the line); which will never happen because both sides are blocked in the constructor of ObjectInputStream.

This can be inferred from the Javadoc of ObjectInputStream(InputStream in):

java文档内容如下:

Creates an ObjectInputStream that reads from the specified InputStream. A serialization stream header is read from the stream and verified. This constructor will block until the corresponding ObjectOutputStream has written and flushed the header

总结:

当错误使用new ObjectOutputStream(OutputStream out)和new ObjectInputStream(InputStream out)的顺序时,会导致服务端和客户端都阻塞等待对方发消息从而产生死锁。