Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the += operator thread-safe in Java?

I found the following Java code.

for (int type = 0; type < typeCount; type++)     synchronized(result) {         result[type] += parts[type];     } } 

where result and parts are double[].

I know basic operations on primitive types are thread-safe, but I am not sure about +=. If the above synchronized is necessary, is there maybe a better class to handle such operation?

like image 694
Piotr Stapp Avatar asked Sep 20 '15 07:09

Piotr Stapp


People also ask

Is ++ operator thread-safe in Java?

Example of Non-Thread-Safe Code in Java The above example is not thread-safe because ++ (the increment operator) is not an atomic operation and can be broken down into reading, update, and write operations.

Is thread-safe in Java?

Since String is immutable in Java, it's inherently thread-safe. 2) Read-only or final variables in Java are also thread-safe in Java. 3) Locking is one way of achieving thread-safety in Java. 4) Static variables if not synchronized properly become a major cause of thread-safety issues.

What is thread-safe operations?

Thread safety is the avoidance of data races—situations in which data are set to either correct or incorrect values, depending upon the order in which multiple threads access and modify the data.

Which variables are thread-safe in Java?

On its stack(basically thread stack), local primitives and local reference variables are stored. Hence one thread does not share its local variables with any other thread as these local variables and references are inside the thread's private stack. Hence local variables are always thread-safe.


Video Answer


2 Answers

No. The += operation is not thread-safe. It requires locking and / or a proper chain of "happens-before" relationships for any expression involving assignment to a shared field or array element to be thread-safe.

(With a field declared as volatile, the "happens-before" relationships exist ... but only on read and write operations. The += operation consists of a read and a write. These are individually atomic, but the sequence isn't. And most assignment expressions using = involve both one or more reads (on the right hand side) and a write. That sequence is not atomic either.)

For the complete story, read JLS 17.4 ... or the relevant chapter of "Java Concurrency in Action" by Brian Goetz et al.

As I know basic operations on primitive types are thread-safe ...

Actually, that is an incorrect premise:

  • consider the case of arrays
  • consider that expressions are typically composed of a sequence of operations, and that a sequence of atomic operations is not guaranteed to be atomic.

There is an additional issue for the double type. The JLS (17.7) says this:

"For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write."

"Writes and reads of volatile long and double values are always atomic."


In a comment, you asked:

So what type I should use to avoid global synchronization, which stops all threads inside this loop?

In this case (where you are updating a double[], there is no alternative to synchronization with locks or primitive mutexes.

If you had an int[] or a long[] you could replace them with AtomicIntegerArray or AtomicLongArray and make use of those classes' lock-free update. However there is no AtomicDoubleArray class, or even an AtomicDouble class.

(UPDATE - someone pointed out that Guava provides an AtomicDoubleArray class, so that would be an option. A good one actually.)

One way of avoiding a "global lock" and massive contention problems might be to divide the array into notional regions, each with its own lock. That way, one thread only needs to block another thread if they are using the same region of the array. (Single writer / multiple reader locks could help too ... if the vast majority of accesses are reads.)

like image 62
Stephen C Avatar answered Sep 21 '22 06:09

Stephen C


Despite of the fact that there is no AtomicDouble or AtomicDoubleArray in java, you can easily create your own based on AtomicLongArray.

static class AtomicDoubleArray {     private final AtomicLongArray inner;      public AtomicDoubleArray(int length) {         inner = new AtomicLongArray(length);     }      public int length() {         return inner.length();     }      public double get(int i) {         return Double.longBitsToDouble(inner.get(i));     }      public void set(int i, double newValue) {         inner.set(i, Double.doubleToLongBits(newValue));     }      public void add(int i, double delta) {         long prevLong, nextLong;         do {             prevLong = inner.get(i);             nextLong = Double.doubleToLongBits(Double.longBitsToDouble(prevLong) + delta);         } while (!inner.compareAndSet(i, prevLong, nextLong));     } } 

As you can see, I use Double.doubleToLongBits and Double.longBitsToDouble to store Doubles as Longs in AtomicLongArray. They both have the same size in bits, so precision is not lost (except for -NaN, but I don't think it is important).

In Java 8 the implementation of add can be even easier, as you can use accumulateAndGet method of AtomicLongArray that was added in java 1.8.

Upd: It appears that I virtually re-implemented guava's AtomicDoubleArray.

like image 24
Aivean Avatar answered Sep 22 '22 06:09

Aivean