in what way is java.net.Socket threadsafe?
Asked Answered
D

6

11

I have a Socket that I am both reading and writing to, via BufferedReaders and BufferedWriters. I'm not sure which operations are okay to do from separate threads. I would guess that writing to the socket from two different threads at the same time is a bad idea. Same with reading off the socket from two different threads at the same time. What about reading on one thread while writing on another?

I ask because I want to have one thread blocked for a long time on a read as it waits for more data, but during this wait I also have occasional data to send on the socket. I'm not clear if this is threadsafe, or if I should cancel the read before I write (which would be annoying).

Decagon answered 12/7, 2011 at 23:25 Comment(0)
N
5

You actually read from InputStream and write to OutputStream. They are fairly independent and for as long as you serialize access to each of them you are ok.

You have to correlate, however, the data that you send with data that you receive. That's different from thread safety.

Nobile answered 12/7, 2011 at 23:38 Comment(3)
What do you mean as "serialize access to each of them"?Narco
@gasan: don't try to write with two threads at once, or read with two threads at once. Call the reads/writes from some synchronized block/method (one lock for read, one for write).Bartz
>>> as long as you serialize access to each of them: That is not enough to make the access thread-safe, you have to make sure that the Socket state is managed using the same lock that is used to serialize the access. I recommend, simply don't do it. Use buffers, and manage socket reads/writes in one threadPterous
F
7

Sockets are thread unsafe at the stream level. You have to provide synchronization. The only warranty is that you won't get copies of the exact same bytes in different read invocations no matter concurrency.

But at a Reader and, specially, Writer level, you might have some locking problems.

Anyway, you can handle read and write operations with the Socket's streams as if they were completely independent objects (they are, the only thing they share is their lifecyle).

Once you have provided correct synchronization among reader threads on one hand, and writer threads on the other hand, any number of readers and writers will be okay. This means that, yes, you can read on one thread and write on another (in fact that's very frequent), and you don't have to stop reading while writing.

One last advice: all of the operations involving threads have associated timeout, make sure that you handle the timeouts correctly.

Firewarden answered 12/7, 2011 at 23:58 Comment(1)
Even getInputStream() is not thread save, as it does isConnected() in a not thread-safe fashion. This is enough not to use it by multiple threads at all if you do not provide synchronization.Pterous
N
5

You actually read from InputStream and write to OutputStream. They are fairly independent and for as long as you serialize access to each of them you are ok.

You have to correlate, however, the data that you send with data that you receive. That's different from thread safety.

Nobile answered 12/7, 2011 at 23:38 Comment(3)
What do you mean as "serialize access to each of them"?Narco
@gasan: don't try to write with two threads at once, or read with two threads at once. Call the reads/writes from some synchronized block/method (one lock for read, one for write).Bartz
>>> as long as you serialize access to each of them: That is not enough to make the access thread-safe, you have to make sure that the Socket state is managed using the same lock that is used to serialize the access. I recommend, simply don't do it. Use buffers, and manage socket reads/writes in one threadPterous
P
2

Java java.net.Socket is not actually thread safe: Open the Socket source, and look at the (let say) connected member field and how it is used. You will see that is not volatile, read and updated without synchrinization. This indicates that Socket class is not designed to be used by multiple threads. Though, there is some locks and synchronization there, it is not consistent.`

I recommend not to do it. Eventually, use buffers(nio), and do socket reads/writes in one thread

For details go the the discussionv

Pterous answered 12/7, 2011 at 23:48 Comment(16)
There's an awful lot of code out there that would break if this was even remotely true. The 'connected' member is only set once in the life of the socket. The basic issue is that the input stream is separate from the output stream and the underlying TCP stream is full duplex: there is no thread-unsafety there.Molarity
@EJP it is not only connected, another one is created. Do you want to have random behavior because the SocketImpl is not properly initialized when trying to use it?Pterous
I don't think that there is a lot of code that accesses single open Socket from different threads. That is because, usually, you have a protocol and you write based on what you've read before and other way around. Doing this in different threads (IMO) is very unusualPterous
>>> the input stream is separate from the output stream If those are different objects, doesn't mean that they do not access the same state of the socket. Browse a little bit the sources of Socket, SocketImpl, PlainSocketImpl, etc and you will see that the streams actually have state in common. The class not being designed for multi-threading (give me java-doc/spec that says it is safe) is reason enough not to do itPterous
>>> There's an awful lot of code Give me an example of some FOSS project that is doing that.Pterous
Your questions are futile. Obviously nobody wants random behaviour. The question is whether there will really be any random behaviour. I've never seen any, in 14 years. As I said, there is an awful lot of code that would break if your contention was correct. RMI/Jeri in Jini is a very prominent example of code that reads and writes in separate threads.Molarity
>>> TCP stream is full duplex: This means that there are separate byte streams for in and out, but does not mean that you can use the socket library (java, libc, etc) in concurrent fashion.Pterous
Oh come off it. There is no socket support whatsover in libc so that is completely irrelevant. We are discussing the Java API in java.net. What are all the acquireFD()/releaseFD() calls for it if it isn't intended to be used by multiple threads?Molarity
>>> There is no socket support whatsover in libc gnu.org/s/hello/manual/libc/index.html#toc_SocketsPterous
The glibc example uses system calls only: socket(), bind(), connect(), etc, and they are all threadsafe. There is no library to speak of, other than the DNS functions.Molarity
>> I've never seen any, in 14 years I am not talking about what you've seen, i am talking about what is written in the java.net.Socket & friendsPterous
'I don't think that there is a lot of code': We are not talking about what you think either, we are talking about facts. You haven't answered my question about acquiring and releasing FDs and you haven't addressed my very conspicuous counter-example to what you 'don't think'.Molarity
>>> acquiring and releasing FDs FDs are part of the implementation of PlainSocketImpl and are used only to transfer control to the native code. .... And even the FDs are accessed in inconsistent way as they are not volatile, neither access to them is always synchronizedPterous
@EJP let us continue this discussion in chatPterous
Re the 'discussion' you've now linked to, there is no 'discussion', there, it is just a copy of all these comments up to but excluding this point. You still haven't addressed my point about all the zillions of lines of code, starting with Java RMI itself, that would fail if there was any synchronization issue with Sockets. Let's be realistic. Regardless of the state of the Javadoc etc there is absolutely zero possibility of things being changed so that RMI breaks.Molarity
@Molarity Actually, there is an indication in the Java documentation that a socket may be accessed from different threads: “close - Any thread currently blocked in an I/O operation upon this socket will throw a SocketException.”Wellspoken
V
1

You can have one thread reading the socket and another thread writing to it. You may want to have a number of threads write to the socket, in which case you have to serialize your access with synchronization or you could have a single writing thread which gets the data to write from a queue. (I prefer the former)

You can use non-blocking IO and share the reading and writing work in a single thread. However this is actually more complex and tricky to get right. If you want to do this I suggest you use a library to help you such as Netty or Mina.

Vulgarity answered 13/7, 2011 at 7:58 Comment(0)
H
0

Very interesting, the nio SocketChannel writes are synchronized

http://www.docjar.com/html/api/sun/nio/ch/SocketChannelImpl.java.html

The old io Socket stuff depends on the OS so you would have to look at the OS native code to know for sure(and that may vary from OS to OS)...

Just look at java.net.SocketOutputStream.java which is what Socket.getOutputStream returns.

(unless of course I missed something).

oh, one more thing, they could have put synchronization in the native code in every JVM on each OS but who knows for sure. Only the nio is obvious that synchronization exists.

Hatti answered 11/4, 2012 at 23:54 Comment(0)
B
0

This is how socketWrite in native code, so it's not thread safe from the code

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);
    }
}
Besant answered 12/12, 2016 at 5:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.