简体   繁体   中英

Interruptible network I/O in Java

In Java 1.4+, there're 3 ways to interrupt a stream which is blocked on socket I/O:

  1. If the socket was created using a regular java.net.Socket(InetAddress, int) constructor, I can close it from a separate thread. As a result, a SocketException is thrown in the blocked thread.
  2. If the socket was created using SocketChannel.open(...) . socket() (non-blocking I/O) — again, it is possible to close it from a separate thread, but now a different exception (an AsynchronousCloseException ) is thrown in the blocked thread.
  3. Additionally, in case non-blocking I/O is used, it is possible to interrupt a blocked thread, with a ClosedByInterruptException thrown. Interrupting a blocked thread when using old-style Java I/O has no effect on the thread.

Questions:

  1. Is closing a socket from a separate thread thread-safe when using old-style I/O? If not, what are the alternatives?
  2. Is closing a socket/channel from a separate thread thread-safe when using NIO instead?
  3. Is there any difference in Socket.close() behaviour when using NIO as opposed to regular IO?
  4. Are there any benefits of using NIO for networking other than a possibility to terminate a blocked I/O operation by simply interrupting a thread (so that I no longer need to keep a reference to the socket)?

Is closing a socket from a separate thread thread-safe when using old-style I/O? If not, what are the alternatives?

yes.

An alternative is to use blocking NIO (which is the default behaviour for a SocketChannel BTW) I prefer this for a small number of connections as it has the efficiency of NIO, but some of the simplicity of Plain IO.

Is closing a socket/channel from a separate thread thread-safe when using NIO instead?

For both blocking NIO, and no blocking NIO, they are thread safe.

Is there any difference in Socket.close() behaviour when using NIO as opposed to regular IO?

If you need to know the details, I suggest you read the code, but basically they are the same.

Are there any benefits of using NIO for networking other than a possibility to terminate a blocked I/O operation by simply interrupting a thread (so that I no longer need to keep a reference to the socket)?

How you close a connection is the least of concerns. So yes, there are many reasons to consider NIO over plain IO.

Pros for NIO

  • Faster and more light weight when using direct memory
  • More scalable to tens of thousands of users.
  • Supports efficient busy waiting.

Cons

  • Plain IO is simpler to code.
  • Many APIs only support plain IO for this reason.

A little late to the party, and the answers already cover the topic, but I think I can still add something useful.

I'll try to be clear about what guarantees are made by the API spec, and what is implementation specific (using Oracle's Java 8 sources for details). For example, if something is safe in Oracle's Java 8, but the API doesn't guarantee that, it may suddenly break in another vendor's implementation or in a different version of Java.

TL;DR: Even for blocking stream-based IO, NIO is IMHO much better. Just make sure to use Socket.getInputStream() and Socket.getOutputStream() instead of Channels.newInputStream() and Channels.newOutputStream() . And code defensively against possible API spec violations (such as wrong exceptions being thrown or null returned unexpectedly).

  1. Yes, old-school Socket.close() is thread safe.

    API-wise: to quote the docs, “Any thread currently blocked in an I/O operation upon this socket will throw a SocketException .” It doesn't say “safe” anywhere, but such a guarantee would be impossible without thread-safety.

    Implementation-wise: Oracle's Java 8 Socket implementation is rather awful when it comes to thread safety. A lot of different locks used or not used in random places. One major concern is whether it's possible to split reads and writes on the same socket between two threads. That's something that seems to work, but not guaranteed by the API spec, and the code looks like it could randomly break at any moment. However, as far as close() is concerned, it is protected by locking on the Socket itself and some internal lock, making it really safe.

    An (obvious) warning applies, though: close() itself is thread-safe, but in order it to work as expected, general thread-safety rules have to be followed when accessing the socket instance, that is, it should be correctly published to the thread that performs the close() .

  2. NIO is generally very thread-safe, both in the spec and in the real implementation. close() is no different. Implemenation-wise, SocketChannel.socket() returns an instance of SocketAdaptor which is a case of the Adaptor Pattern, adapting a SocketChannel to the Socket API. The old Socket implementation is not used at all by NIO, all Socket operations are delegated to the underlying SocketChannel .

  3. The API is of little help here, because SocketChannel.socket() “officially” returns a reference to a Socket , whose docs say nothing about NIO at all. On one hand, it is just as it should be, considering backwards compatibility and programming to interface. On the other hand, implementation-wise SocketAdaptor does a poor job adapting to Socket interface, though, at least in Oracle's Java 8. For example, SocketInputStream doesn't really try to wrap exceptions in their Socket equivalent. That's why you see AsynchronousCloseException when the docs promise a SocketException to be thrown.

    The good news is that the NIO implementation is generally better. So as long as you don't mind the wrong exceptions being thrown (and why would anyone care what kind of IOException is it?), a NIO Socket does its job. As far as close() goes, it just closes the associated channel, so it doesn't really matter whether you call Socket.close() or SocketChannel.close() .

  4. @Peter Lawrey covered general NIO-vs-IO pros and cons pretty well. So instead of talking of NIO-vs-IO in general, I'll assume that we're working only with blocking stream-based IO. In this case, NIO has the following pros:

    • It is fully thread-safe. I've already mentioned parallel reads and writes (a typical situation for asynchronous protocols when you're sending a long stream of data and must be ready to receive messages from the other side in the process). While it seems to work with IO, it is guaranteed to work with NIO.

    • You mentioned the ability to close the socket by interrupting a thread, but it's often underrated. One may think that it's not much of a big difference between Thread.interrupt() and Socket.close() , but in fact it is. If your thread is doing more than just IO, you'll have to call both (Does the order matter? Not very obvious.) Another consideration: if you use Executors for threading (which you should), they know nothing about your sockets, but they know everything about interrupting threads. With NIO, you can just cancel a Future or shutdown() an Executor . With IO, you have to somehow deal with your socket too.

    Cons:

    • Mixing NIO with IO can be tricky. You may think that SocketChannel.socket().getInputStream() is equivalent of Channels.newInputStream(SocketChannel) . Well, it isn't. The former supports SocketTimeoutException , the latter doesn't (it just blocks forever). This can be very important for protocols when you're supposed to receive heartbeat messages and close the connection if they stop coming.

    • Another case when NIO breaks IO API spec is Socket.getRemoteSocketAddress() / SocketChannel.getRemoteAddress() . According to Socket docs, “If the socket was connected prior to being closed, then this method will continue to return the connected address after the socket is closed.” Well, for NIO that's not true. SocketChannel.getRemoteAddress() will throw ClosedChannelException , as it should, but Socket.getRemoteSocketAddress() will return null instead of the correct address. This may not seem like a big deal, but it could potentially lead to NPE in places you least expect it.

1) Since the underlying OS call error that rasies the exception comes from a TCP stack that is multithread aware, there should not be any problem.

2) Not sure- have not tried it, but would be surprised if there was any issue.

3) There may be some performance differences. An OS close() call requires a 4-way handshake with the TCP peer - not sure which OS support that in a non-blocking manner, (same with connect).

4) Thread..or socket. You have to keep a reference to something. Since you are closing the socket, it seems reasonable to keep a reference to it :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM