一个 有点 迟到了,答案已经覆盖的话题,但我想我还是可以添加一些有用的东西。
我将尝试弄清楚API规范做出了哪些保证,以及具体实现是什么(有关详细信息,请使用Oracle的Java 8源)。例如,如果Oracle Java
8中的某些内容是安全的,但是API不能保证这一点,那么它可能会突然中断其他供应商的实现或其他Java版本。
TL;
DR:即使对于阻止基于流的IO,NIO也要好得多。只要确保使用
Socket.getInputStream()和
Socket.getOutputStream()而不是
Channels.newInputStream()和即可
Channels.newOutputStream()。并针对可能违反API规范的代码进行防御(例如,错误的异常被
null意外抛出或返回)。
- 是的,老派
Socket.close()
是线程安全的。
API方面:引用文档,“当前在此套接字上的I / O操作中阻塞的任何线程都会抛出
SocketException。”
它在任何地方都没有说“安全”,但是如果没有线程安全,这样的保证是不可能的。
实现方面:
Socket就线程安全而言,Oracle的Java 8
实现相当糟糕。很多随机使用或未使用过的锁。一个主要的问题是是否可以在两个线程之间的同一套接字上拆分读写。这 似乎
可行,但API规范无法保证,并且代码看起来随时可能会随机中断。但是,就其
close()本身而言,它通过
Socket自身的锁定和一些内部锁定得到保护,从而使其真正安全。
但是,有一个(明显的)警告:
close()本身是线程安全的,但是为了使其按预期工作,访问套接字实例时必须遵循常规的线程安全规则,即,应将其正确发布到线程中。执行
close()。
在规范和实际实现中,NIO通常都是非常线程安全的。
close()
没什么两样 在实现方面,SocketChannel.socket()
返回一个实例,SocketAdaptor
该实例是Adapter Pattern的一种情况,适用SocketChannel
于Socket
API。Socket
NIO完全不使用旧的实现,所有Socket
操作都委托给底层SocketChannel
。该API在这里无济于事,因为
SocketChannel.socket()
“正式”返回对的引用Socket
,其文档完全没有提及NIO。一方面,它是应有的,考虑了向后兼容性和接口编程。另一方面,至少在Oracle Java 8中,实现方面SocketAdaptor
在适应Socket
接口方面做得很差。例如,SocketInputStream
实际上并没有尝试将异常包装为Socket
等效形式。这就是为什么您看到AsynchronousCloseException
文档承诺何时SocketException
抛出a 的原因。
好消息是,NIO实施通常更好。因此,只要您不介意抛出错误的异常(为什么有人会关心
IOException它是什么类型?),NIO就会发挥
Socket作用。就目前
close()而言,它只是关闭了关联的频道,因此无论您呼叫
Socket.close()还是都没有关系
SocketChannel.close()。
@Peter Lawrey很好地介绍了NIO-vs-IO的一般优缺点。因此 , 我将不再假定 一般地 谈论NIO-vs-IO , 而是假定我们 仅 在处理基于流的IO。在这种情况下,NIO具有以下优点:
这是完全线程安全的。我已经提到过并行读写(异步协议的一种典型情况,当您发送长数据流并且必须准备好从过程另一端接收消息时)。尽管它 似乎 可以与IO一起使用,但可以 保证 它可以与NIO一起使用。
您提到了通过中断线程来关闭套接字的功能,但是它经常被低估。可能会认为
Thread.interrupt()
和之间的差别不大Socket.close()
,但实际上是。如果您的线程所做的不仅仅是I / O,则必须同时调用 两者 (顺序很重要吗?不是很明显。)另一个注意事项:如果Executors
用于线程化(应该这样做),则它们对套接字一无所知,但是他们对中断线程一无所知。随着NIO,你可以取消一个Future
或shutdown()
一个Executor
。使用IO,您还必须以某种方式处理您的套接字。
缺点:
* 将NIO与IO混合可能很棘手。您可能会认为`SocketChannel.socket().getInputStream()`等同于`Channels.newInputStream(SocketChannel)`。好吧,不是。前者支持`SocketTimeoutException`,后者则不支持(它只会永远阻止)。当您应该接收心跳消息并在连接停止时关闭连接时,这对于协议非常重要。* NIO违反IO API规范的另一种情况是`Socket.getRemoteSocketAddress()`/ `SocketChannel.getRemoteAddress()`。根据`Socket`文档,“如果套接字在关闭之前已连接,则此方法将在套接字关闭后继续返回连接的地址。” 好吧,对于NIO而言,这是不正确的。`SocketChannel.getRemoteAddress()`将会抛出`ClosedChannelException`,但应该`Socket.getRemoteSocketAddress()`返回,`null`而不是正确的地址。这看起来似乎没什么大不了的,但可能会在您最不希望的地方导致NPE。



