我有一个通过 BufferedReaders 和 BufferedWriters 读写的 Socket。我不确定哪些操作可以从单独的线程中执行。我猜想同时从两个不同的线程写入套接字是一个坏主意。与同时从两个不同线程读取套接字相同。在一个线程上阅读而在另一个线程上写作呢?
我问是因为我想让一个线程在读取时长时间阻塞,因为它等待更多数据,但在此等待期间,我偶尔也会在套接字上发送数据。我不清楚这是否是线程安全的,或者我是否应该在写之前取消读取(这会很烦人)。
我有一个通过 BufferedReaders 和 BufferedWriters 读写的 Socket。我不确定哪些操作可以从单独的线程中执行。我猜想同时从两个不同的线程写入套接字是一个坏主意。与同时从两个不同线程读取套接字相同。在一个线程上阅读而在另一个线程上写作呢?
我问是因为我想让一个线程在读取时长时间阻塞,因为它等待更多数据,但在此等待期间,我偶尔也会在套接字上发送数据。我不清楚这是否是线程安全的,或者我是否应该在写之前取消读取(这会很烦人)。
套接字在流级别是线程不安全的。您必须提供同步。唯一的保证是,无论并发如何,您都不会在不同的读取调用中获得完全相同字节的副本。
但是在 Reader,特别是 Writer 级别,您可能会遇到一些锁定问题。
无论如何,您可以使用 Socket 的流处理读写操作,就好像它们是完全独立的对象一样(它们是,它们唯一共享的就是它们的生命周期)。
一旦您一方面在读取器线程之间提供了正确的同步,另一方面在写入器线程之间提供了正确的同步,任何数量的读取器和写入器都可以。这意味着,是的,您可以在一个线程上阅读并在另一个线程上写入(实际上这是非常频繁的),并且您不必在写作时停止阅读。
最后一条建议:所有涉及线程的操作都有相关的超时,请确保正确处理超时。
您实际上是从 InputStream 读取并写入 OutputStream。它们是相当独立的,只要您对它们中的每一个进行序列化访问就可以了。
但是,您必须将发送的数据与接收的数据关联起来。这与线程安全不同。
Javajava.net.Socket
实际上不是线程安全的:打开 Socket 源代码,然后查看(比如说)connected
成员字段以及它是如何使用的。你会看到不是,没有同步volatile
的读取和更新。这表明 Socket 类不是为多线程使用而设计的。虽然那里有一些锁和同步,但并不一致。
我建议不要这样做。最终,使用缓冲区(nio),并在一个线程中进行套接字读/写
有关详细信息,请参阅讨论v
您可以让一个线程读取套接字,而另一个线程写入它。您可能希望有多个线程写入套接字,在这种情况下,您必须通过同步来序列化您的访问,或者您可以有一个写入线程从队列中获取要写入的数据。(我更喜欢前者)
您可以使用非阻塞 IO 并在单个线程中共享读取和写入工作。然而,这实际上更加复杂和棘手。如果你想这样做,我建议你使用一个库来帮助你,比如 Netty 或 Mina。
很有意思,nio SocketChannel 写入是同步的
http://www.docjar.com/html/api/sun/nio/ch/SocketChannelImpl.java.html
旧的 io Socket 东西取决于操作系统,因此您必须查看操作系统的本机代码才能确定(并且可能因操作系统而异)...
看看 java.net.SocketOutputStream.java 是 Socket.getOutputStream 返回的。
(当然,除非我错过了什么)。
哦,还有一件事,他们可以在每个操作系统上的每个 JVM 的本机代码中进行同步,但谁知道呢。只有 nio 明显存在同步。
这就是 socketWrite 在本机代码中的方式,因此它不是代码中的线程安全的
JNIEXPORT void JNICALL
Java_java_net_SocketOutputStream_socketWrite0(JNIEnv *env, jobject this,
jobject fdObj,
jbyteArray data,
jint off, jint len) {
char *bufP;
char BUF[MAX_BUFFER_LEN];
int buflen;
int fd;
if (IS_NULL(fdObj)) {
JNU_ThrowByName(env, "java/net/SocketException", "Socket closed");
return;
} else {
fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
/* Bug 4086704 - If the Socket associated with this file descriptor
* was closed (sysCloseFD), the the file descriptor is set to -1.
*/
if (fd == -1) {
JNU_ThrowByName(env, "java/net/SocketException", "Socket closed");
return;
}
}
if (len <= MAX_BUFFER_LEN) {
bufP = BUF;
buflen = MAX_BUFFER_LEN;
} else {
buflen = min(MAX_HEAP_BUFFER_LEN, len);
bufP = (char *)malloc((size_t)buflen);
/* if heap exhausted resort to stack buffer */
if (bufP == NULL) {
bufP = BUF;
buflen = MAX_BUFFER_LEN;
}
}
while(len > 0) {
int loff = 0;
int chunkLen = min(buflen, len);
int llen = chunkLen;
(*env)->GetByteArrayRegion(env, data, off, chunkLen, (jbyte *)bufP);
while(llen > 0) {
int n = NET_Send(fd, bufP + loff, llen, 0);
if (n > 0) {
llen -= n;
loff += n;
continue;
}
if (n == JVM_IO_INTR) {
JNU_ThrowByName(env, "java/io/InterruptedIOException", 0);
} else {
if (errno == ECONNRESET) {
JNU_ThrowByName(env, "sun/net/ConnectionResetException",
"Connection reset");
} else {
NET_ThrowByNameWithLastError(env, "java/net/SocketException",
"Write failed");
}
}
if (bufP != BUF) {
free(bufP);
}
return;
}
len -= chunkLen;
off += chunkLen;
}
if (bufP != BUF) {
free(bufP);
}
}