Are Java Primitive Ints Atomic by Design or by Accident

Are java primitive ints atomic by design or by accident?

All memory accesses in Java are atomic by default, with the exception of long and double (which may be atomic, but don't have to be). It's not put very clearly to be honest, but I believe that's the implication.

From section 17.4.3 of the JLS:

Within a sequentially consistent
execution, there is a total order over
all individual actions (such as reads
and writes) which is consistent with
the order of the program, and each
individual action is atomic and is
immediately visible to every thread.

and then in 17.7:

Some implementations may find it
convenient to divide a single write
action on a 64-bit long or double
value into two write actions on
adjacent 32 bit values. For
efficiency's sake, this behavior is
implementation specific; Java virtual
machines are free to perform writes to
long and double values atomically or
in two parts.

Note that atomicity is very different to volatility though.

When one thread updates an integer to 5, it's guaranteed that another thread won't see 1 or 4 or any other in-between state, but without any explicit volatility or locking, the other thread could see 0 forever.

With regard to working hard to get atomic access to bytes, you're right: the VM may well have to try hard... but it does have to. From section 17.6 of the spec:

Some processors do not provide the
ability to write to a single byte. It
would be illegal to implement byte
array updates on such a processor by
simply reading an entire word,
updating the appropriate byte, and
then writing the entire word back to
memory. This problem is sometimes
known as word tearing, and on
processors that cannot easily update a
single byte in isolation some other
approach will be required.

In other words, it's up to the JVM to get it right.

primitive datatypes are atomic in java

When I have 2 Threads that increment and decrement on a int variable than sometimes i still got race conditions.

Yes, you will - because even though the "get" and "set" operations on the int variable are each atomic, that doesn't mean the "increment" operation is atomic.

Is atomic in this case every single operation (getfield, iadd...) and not the full addition?

Yes, exactly. It's not actually the primitive types are atomic - it's read and write operations that are atomic. That's a big difference.

volatile declaration on int primitive type

The first statement does not refer to making reference variables and primitive variables (except long and double) volatile.

It says reads and writes of all reference variables and all primitives except long and double are atomic (by default). To make reads and writes of long and double atomic they need to be volatile.

Atomicity does not have anything to do with visibility.

The following paragraph on the same doc

Atomic actions cannot be interleaved, so they can be used without fear of thread interference. However, this does not eliminate all need to synchronize atomic actions, because memory consistency errors are still possible. Using volatile variables reduces the risk of memory consistency errors, because any write to a volatile variable establishes a happens-before relationship with subsequent reads of that same variable.

So, statements like a = 1 where a is an int (for example) are atomic but you still need to have volatile if you want the assignment to be visible for any subsequent reading threads.

Reading/Writing to a long/double variable is a compound action and making it volatile ensures that it is atomic.

Does atomic actually mean anything for a synthesized primitive?

Primitive types aren't guaranteed to be safe from being partially modified because modifications to primitive types aren't guaranteed to be atomic in C and Objective-C inherits from there. C's only guarantees concern sequence points and there's no requirement that the processing between two sequence points be atomic — a rule of thumb is that each full expression is a sequence point.

In practice, modifying primitives is often a two-step process; the modification is made in a register and then written out to memory. It's very unlikely that the write itself will not be atomic but there's also no guarantee of when it will occur versus the modification. Even with the volatile qualification, the only guarantees provided are in terms of sequence points.

Apple exposes some C functions for atomic actions via OSAtomic.h that map directly to the specialised atomic instructions that CPUs offer for the implementation of concurrency mechanisms. It's possible you could use one of those more directly than via a heavy-handed mutex.

Common patterns in Objective-C are:

  • immutable objects and functional transformations — there are memory management reasons as well but that's partly why NSString, NSArray, etc, are specifically distinct from NSMutableString, NSMutableArray, etc;
  • serial dispatch queues, which can be combined with copy-modify-replace by copying on the queue, going off somewhere else to modify, then jumping back onto the queue to replace;
  • such @synchronizeds, NSConditionLocks or other explicit synchronisation mechanisms as are appropriate.

The main thread itself is a serial dispatch queue, which is why you can ignore the issue of concurrency entirely if you restrict yourself to it.

Is writing a reference atomic on 64bit VMs

Reading/writing references always atomic

See JLS section 17.7: Non-atomic Treatment of double and long

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.

Writes to and reads of references are always atomic, regardless of
whether they are implemented as 32-bit or 64-bit values.

Some implementations may find it convenient to divide a single write
action on a 64-bit long or double value into two write actions on
adjacent 32-bit values. For efficiency's sake, this behavior is
implementation-specific; an implementation of the Java Virtual Machine
is free to perform writes to long and double values atomically or in
two parts.

Implementations of the Java Virtual Machine are encouraged to avoid
splitting 64-bit values where possible. Programmers are encouraged to
declare shared 64-bit values as volatile or synchronize their programs
correctly to avoid possible complications.

(Emphasis added)

AtomicReference

If you want to coordinate between old and new values, or want specific memory effects, use the class AtomicReference.

For example, AtomicReference::getAndSet returns the old value while setting the new value atomically, eliminating any chance of another thread having intervened between the two steps. Uses volatile memory semantics.

Atomicity and memory order in Java

No.

If you use data structures like AtomicInteger, then yes, set() operation implies a strong memory order, just like some other operations in the class. The word "Atomic" is used here to express that the class implement a set of atomic operations (e.g. compareAndSet) that ordinarily would be composed of sub-operations. Incidentally, these kind of data structures often follow strong memory orders, and by word association, "atomic", used in this kind of context, often implies strong memory order. (But not always true though, e.g. lazySet and weakCompareAndSet in AtomicInteger)

Back to your question, the word "atomic" there has no such connotation, it simply means the write is indivisible. But that is meaningless. What is meaningful is to say that a write to a non-volatile long or double variable is "non-atomic" because it is equivalent to two writes (JLS#17.7). Other writes are not-non-atomic.

Are atomic objects protected against race conditions?

Yes, you are correct that non-atomic operations may still have race condition. If you have non-atomic operations that depend on the state of the atomic object without interference from other threads, you need to use another synchronization technique to maintain consistency.

Atomic operations on the atomic object will be consistent, but not race-free. Non-atomic operations using the atomic object are not race-free.



Related Topics



Leave a reply



Submit