14

What options do I have to make a ByteBuffer thread safe? It is known that it is not thread safe as it safes position, limit and some(/all?) methods depend on this internal state.

For my purposes it will be sufficient if multiple read-threads are safe, but for other future visitors I would like to know what technics/tricks/traps I need to know to make it completely thread safe.

What I have in mind:

  • Synchronizing or using ReadWrite locks for all methods. Probably the slowest approach (?)
  • Subclassing ByteBuffer and avoid persisting thread-bound state like position etc. And throwing exceptions accordingly for all methods which need to use internal state. This would be the fastes. But are there any traps? (except that I'll have to read the directly mapped memory into heap memory ...)

What other tricks could I use? How would I e.g. implement a "clone bytes on read" with DirectBuffer - is it possible at all? Would probably slicing the full ByteBuffer (ByteBuffer.slice) be involved in one solution?

Update: What is meant in this question with "duplicate (while synchronized) to get a new instance pointing to the same mapped bytes"

Community
  • 1
  • 1
Karussell
  • 17,085
  • 16
  • 97
  • 197
  • 1
    I prefer to use an actor model where any ByteBuffer is updated by only one thread (or is read only). The reason I do this is I have found the overhead of synchronization usually outweighs the benefits of having multiple threads. – Peter Lawrey Jun 22 '12 at 13:39
  • thanks, peter! are you using this via slice? or how do you access the same data? – Karussell Jun 22 '12 at 15:20
  • I don't use a slice, instead I use a wrapper which hides everything (even the fact I am using ByteBuffers and I may want to switch to Unsafe which is slightly faster) The benefit of using a wrapper is that you can hide all the details and access your "array" naturally by calling methods. This is particularly useful as I have arrays with billions of elements which won't fit into a single ByteBuffer. – Peter Lawrey Jun 22 '12 at 15:23
  • Do I have luck and you Buffer wrapper is open source ;) ? – Karussell Jun 23 '12 at 16:33
  • No, but I have a similar library which is open source. https://github.com/peter-lawrey/Java-Chronicle This is entry/row/excerpt based rather than column based, but the techniques are the same. – Peter Lawrey Jun 24 '12 at 09:31
  • Hmmh, is it thread safe? I couldn't find any synchs or locks ... – Karussell Jun 26 '12 at 14:06
  • Each chronicle is single threaded, you need to create one for each thread or process. As each thread is much faster you only need a few (possibly only one) It doesn't use locks, and is mostly heapless. – Peter Lawrey Jun 26 '12 at 14:28
  • hmmh maybe I already sleep (a bit late here) :) ... but again: what would be the difference to one buffer instance per thread (via Buffer.slice) ... hopefully I won't regret this question tomorrow ;) – Karussell Jun 26 '12 at 22:38
  • An ecxelent question. You would think it is redundant esp as I don't using the underlying ByteBuffer.position() (I use offsets only) The problem is that when you memory map a file in a thread, that is not immediately available to another thread and can randomly result in a bus error. :( This appears to be a low level OS issue on Centos at least. I haven't tried this on Windows. – Peter Lawrey Jun 27 '12 at 08:08

2 Answers2

13

A Buffer class could be made thread-safe ... in the sense that the individual operations were properly synchronized, etcetera. However, the API is not designed with multiple threads in mind, so this this is probably a waste of time.

The basic problem is that the individual operations on a Buffer are too fine-grained to be the unit of synchronization. An application cannot meaningfully synchronize at the level of the get and put operations, or flip, position and so on. Generally speaking, an application needs to perform sequences of these operations atomically in order to synchronize effectively.

The second problem is that if you do synchronize at a fine level, this is likely to add significant overheads on the method calls. Since the point of using the Buffer APIs is to do I/O efficiently, this defeats the purpose.


If you do need to synchronize thread access to a shared buffer, it is better to use external synchronization; e.g. something like this:

    synchronized (someLock) {
        buffer.getByte();
        buffer.getLong();
        ...
    }

Provided all threads that use a given buffer synchronize properly (e.g. using the same lock object), it doesn't matter that the Buffer is not thread-safe. Thread safety is managed externally to the buffer object, and in a more coarse-grained fashion.


As comments point out, you could also use ByteBuffer.slice() or buffer.asReadOnlyBuffer() to give you another buffer with the existing one as backing. However, the javadocs do not guarantee thread-safety in either case. Indeed, the javadocs for Buffer make this blanket statement:

Buffers are not safe for use by multiple concurrent threads. If a buffer is to be used by more than one thread then access to the buffer should be controlled by appropriate synchronization.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • I don't think the read-only case simplifies things, unless you are prepared to treat the buffer as a simple array ... fetch and use the backing array directly via `array()`. Even then you need some synchronization to ensure memory is access consistently. – Stephen C Jun 22 '12 at 12:50
  • 2
    how could it be inconsistent when no write happen? also the backing array is not always existent – Karussell Jun 22 '12 at 15:20
  • 1
    @Karussell - 1) because most operations update the buffer's "position". 2) yes that is true. But it is either that or the getXxx(pos) methods – Stephen C Jun 23 '12 at 00:34
  • ok, thanks! Assuming I would have a read-only operation and using ByteBuffer.slice (so that I can use one ByteBuffer per thread) - are they still any traps :) ? – Karussell Jun 23 '12 at 16:32
  • You can have shareable read-only buffer when you pass a copy created using `buffer.asReadOnlyBuffer()` to each thread. They'll share internal data array, but each will have its own copy of `limit`, `position` and `mark`. – Oliv Oct 23 '17 at 07:48
0

With JDK13 you can now use ByteBuffer without byteBuffer.position(int) and get thread-safety.

See the release notes.

java.nio.ByteBuffer and the other buffer types in java.nio now define absolute bulk get and put methods to transfer contiguous sequences of bytes without regard to or effect on the buffer position.

Karussell
  • 17,085
  • 16
  • 97
  • 197
  • 2
    This has nothing to do with thread-safety. Even the jdk-16 javadoc for Buffer clearly states that "Buffers are not safe for use by multiple concurrent threads". – Harald Apr 18 '21 at 13:18