25

As far as I know reference assignment is atomic in a 64 bit JVM. Now, I assume the jvm doesn't use atomic pointers internally to model this, since otherwise there would be no need for Atomic References. So my questions are:

Is atomic reference assignment in the "specs" of java/Scala and guaranteed to happen or is it just a happy coincidence that it is that way most times ?

Is atomic reference assignment implied for any language that compiles to the JVM's bytecode (e.g. clojure, Groovy, JRuby, JPython...etc) ?

How can reference assignment be atomic without using an atomic pointer internally ?

George
  • 3,521
  • 4
  • 30
  • 75
  • @JornVernee I meant assignment not swapping, thanks for the correction – George May 31 '17 at 11:21
  • This might give a first clue: https://stackoverflow.com/questions/23232242/are-all-instructions-in-jvm-atomic – GhostCat May 31 '17 at 11:35
  • I did... however I've been searching all over the internet for how getfield, putfiled and iastore are implemented and I haven't found an explaination that doesn't involve digging through the openjvm code, which I'm not proficient enough to do :( – George May 31 '17 at 12:23

3 Answers3

31

First of all, reference assignment is atomic because the specification says so. Besides that, there is no obstacle for JVM implementors to fulfill this constraint, as 64 Bit references are usually only used on 64 Bit architectures, where atomic 64 Bit assignment comes for free.

Your main confusion stems from the assumption that the additional “Atomic References” feature means exactly that, due to its name. But the AtomicReference class offers far more, as it encapsulates a volatile reference, which has stronger memory visibility guarantees in a multi-threaded execution.

Having an atomic reference update does not necessarily imply that a thread reading the reference will also see consistent values regarding the fields of the object reachable through that reference. All it guarantees is that you will read either the null reference or a valid reference to an existing object that actually was stored by some thread. If you want more guarantees, you need constructs like synchronization, volatile references, or an AtomicReference.

AtomicReference also offers atomic update operations like compareAndSet or getAndSet. These are not possible with ordinary reference variables using built-in language constructs (but only with special classes like AtomicReferenceFieldUpdater or VarHandle).

Holger
  • 285,553
  • 42
  • 434
  • 765
  • "Having an atomic reference update does not necessarily imply that a thread reading the reference will also see consistent values regarding the fields of the object reachable through that reference." Are you implying that AtomicReference make an object safe to mutate in a multi-threaded enviornment ? Because I can hardly believe this to be true. As far as I know with or without an AtomicReference you CAN'T modify an object thread safely without mutexes or synchronized – George May 31 '17 at 15:09
  • My question was as to the nature of the 64/32 bits value that is the reference itself no the object stored by the reference. – George May 31 '17 at 15:10
  • 1
    @George: setting an `AtomicReference` guarantees the visibility of all updates made to that object *before* publishing it through that `AtomicReference`, not that you are allowed to make updates *after* that. Publishing an object through an ordinary reference variable does not make any guaranty regarding updates, regardless of whether you make them before or after that. It doesn’t even guaranty that other threads will ever see the new reference. – Holger May 31 '17 at 15:12
  • 1
    You clearly said “*otherwise there would be no need for Atomic References*”, hence, it was required to explain why we still need Atomic References, even if reads and writes to references are atomic per se. – Holger May 31 '17 at 15:16
  • What I said is "I assume the jvm doesn't use atomic pointers internally to model this, since otherwise there would be no need for Atomic References"... it was a figure of speech for saying "Whilst I don't know the java specifications I'm quite sure references aren't modeled internally as an atomic pointer since they do not have all the atomic pointer opeartion hence the need for the AtomicReference" – George May 31 '17 at 15:46
  • "AtomicReference guarantees the visibility of all updates made to that object before publishing it through that AtomicReference" Care to elaborate on this ? "Publishing" is not an action in Java. If by publishing your mean assigning then that is already the case with normal references, is it not ? – George May 31 '17 at 15:48
  • 1
    @George: Let's assume you have 2 threads. In one a normal reference is assigned (`ref = object;`), and in the other one it is directly read afterwards (`Object localObject = ref;`). With normal references you have the guarantee that 1. The assignment is at some point of time visible to other threads and 2. `localObject` gets a valid value - which might not necessarily be the latest one which has been written. If you use AtomicReferences instead you would also have the guarantee that the write will be immediately visible to the other thread and that the read from that thread will pick it up. – Matthias247 May 31 '17 at 15:54
  • 2
    An “atomic pointer” isn’t necessarily an “atomic reference”, in the sense of `AtomicReference`. Hence, it was necessary to point out that references (i.e. the underlying pointers) *are* atomic, still don’t match the functionality of an `AtomicReference`. – Holger May 31 '17 at 15:54
  • 3
    “Publishing” means assigning to a variable that is visible to other threads. And no, there are no guaranties regarding visibility of previous updates when publishing though normal references. If you write `point=new Point(42,100)` and `point` is neither `final` nor `volatile`, other threads *may* see the new reference, while still seeing the default value (`0`) for either, `x` or `y `, or both (in the absence of other synchronization). – Holger May 31 '17 at 15:57
  • Do you have any code snipets to back this up ? I though the whole idea behind Java references was that this kind of torn read was not possible. So if you had a point = new Point(0,0) and then you assigned to it: pointer = new Point(42,100) threads reading at the same time might read either Point(0,0) or Pointer(42,100) but never a value in between as you mention. – George May 31 '17 at 16:30
  • 3
    This is not about an older instance; the reference might have been `null` before the assignment, that doesn’t matter. The issue is, `new Point(42,100)` is not an atomic operation. It creates a new `Point` instance, all fields being at their default value (`0` for `int`), then, the constructor will be executed, which will assign the values `42` to `x` and `100` to `y`, then the reference is assigned to `point`, *from a single thread’s point of view*. A different thread may see these actions out of order, see the reference to the new `Point` instance without seeing the effect of the assignments. – Holger May 31 '17 at 16:36
  • 2
    This doesn’t apply to immutable objects. If you declare `x` and `y` as `final`, they’re immune to such data races. Alternatively, you may declare `point` as `final`, but this implies that the whole action is part of a constructor or initializer. Or, well, declaring `point` as `volatile` ensures that other threads see all previous updates (`x=42; y=100;`) when seeing the new `Point` instance through the `point` reference. – Holger May 31 '17 at 16:37
  • Wait ? Isn't construction guaranteed to be done before the object is assigned ? As in isn't var a = new X() executed as: X() -> memory address assigned to a ? – George May 31 '17 at 17:16
  • 1
    I can’t say anything about Scala, but for Java, when using multi-threading without proper thread safe constructs, it is not guaranteed. You may read [JLS §17.4](https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4), if you are interested. – Holger May 31 '17 at 17:22
  • However the question I didn't ask is related to: ', where atomic 64 Bit assignment comes for free' .... is this true ? I couldn't find any reference to this being the case in a multi-cpu environment. I actually don't know assembler so the only way I could "test" it is to see if gcc optimizez away atomic to T * with march=native and ofast and it doesn't, there are extra instructions generated for the atomic version. But then again its very stupid and uncertain to assume gcc will produce optimal code and, as I said before, I don't know shit about x64 (or any) assembler – George May 31 '17 at 21:04
  • @Holger nitpick: `If you declare x and y as final` it is `OR` not `AND`. It is enough for a single field to be final so that a `StoreStore|LoadStore` barrier to be inserted at the end of the constructor. – Eugene Jun 01 '17 at 07:35
  • 1
    @Eugene: there are no such barriers in the specification. You are drawing wrong conclusions from implementation details. The specification clearly says that this is *not guaranteed*, [even by example](https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#d5e34106). – Holger Jun 01 '17 at 08:13
  • 1
    @George: mind the word “usually” in that sentence. You don’t have to care about these details, as that’s up to the JVM developers. It is, by the way, entirely irrelevant what gcc does, as HotSpot’s JIT compiler generates code directly for the target platform, gcc is not involved. I don’t know what C++’ `atomic` implies, perhaps, it’s like Java’s `AtomicReference`, which, as already explained, does a lot more than just atomic assignment. Of course, the JVM/JIT developers have to know what is needed to get the atomic reference behavior, e.g. usually the correct alignment is required, etc. – Holger Jun 01 '17 at 08:18
  • @Holger yes. I should have said *under the current implementation, DONT Do IT, Listen to the JLS*, thank you. http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/4d9931ebf861/src/share/vm/opto/parse1.cpp#l955 – Eugene Jun 01 '17 at 08:20
10

Atomic reference assignment is in the specs.

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

Quoted from JSR-133: Java(TM) Memory Model and Thread Specification, section 12 Non-atomic Treatment of double and long, http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • Ok, that answers the first part but not the other 2. To be honest the thing that I'm most curios about here is how this behavior happens if references are not atomic pointers and that's related to how one would implement a JVM rather than the specifications for java and/or the jvm. – George May 31 '17 at 12:17
  • True. I on purpose did not pretend to be a wiseguy on matters where I’m not sure. The way I understand it, though, this is a spec of the JVM, so I would expect it to cover all languages running on the JVM. And the way I read it, in requires internal pointer operations to happen atomically (at least as seen from outside the JVM). – Ole V.V. May 31 '17 at 12:25
  • Well yes, but I doubt those are actual atomic pointer operations since otherwise things like compare and swap would also be atomic using standard references. – George May 31 '17 at 12:29
  • 1
    Nice answer - good find; my vote for that. And thanks for leaving out that small part that left room for my answer ;-) – GhostCat May 31 '17 at 12:29
  • 3
    The linked PDF describes the memory model for Java 5 proposed by JSR133. To prove that it indeed made it into the Java 5 JLS and is still there today, it’s better to link to [JLS §17.7](https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.7). – Holger May 31 '17 at 14:38
  • Thanks for pointing that out, @Holger, I agree completely. The quote there is exactly the same. – Ole V.V. May 31 '17 at 14:43
3

As the other answer outlines, the Java Memory Model states that references read/writes are atomic.

But of course, that is the Java language memory model. On the other hand: no matter if we talk Java or Scala or Kotlin or ... in the end everything gets compiled into bytecode.

There are no special bytecode instructions for Java. Scala in the end uses the very same instructions.

Leading to: the properties of that memory model must be implemented inside the VM platform. Thus they must apply to other languages running on the platform as well.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • Since the name was ' JavaTM Memory Model' I was unsure if it referred to the JVM as a whole and as such I assumed there might be instructions to non atomically asign available to other JVM languages – George May 31 '17 at 12:40
  • Back then when that memory model was written, there was only the Java language on the Java VM platform. Methinks. – GhostCat May 31 '17 at 12:41
  • Even now, I don't think there are JVM instructions that aren't used by Java itself. – Jasper-M May 31 '17 at 12:44
  • "But of course, that is the Java language memory model." Not really. It describes behavior of both Java language and Java virtual machines, and generally makes it clear which is which. Mostly it talks about VMs, and this section in particular does too: "Java virtual machines are free to perform writes to long and double values atomically or in two parts". – Alexey Romanov May 31 '17 at 13:23
  • 4
    @Jasper-M: there are. E.g., `swap` is never used by `javac` generated code. In Java 7, the `invokedynamic` instruction was not used by Java itself, which is quite a big feature of that JVM version… – Holger May 31 '17 at 14:26
  • @Holger In my defense, *now* `invokedynamic` is used by java itself. :-p Didn't know about `swap` though. Thanks! – Jasper-M May 31 '17 at 14:29
  • 2
    One thing to keep in mind when drawing this conclusion, is, that this only applies if the concepts of that other language (i.e. references) are compiled 1:1 to the equivalent byte code artifacts. E.g. a reference of a particular language might be so far away from what Java’s object references do, that it doesn’t match a sole byte code reference variable, but, e.g. a compound data structure. For Java, it is impossible to encounter an object reference having a not-yet-initialized vtable, but that doesn’t have to apply to another language, having an entirely different method dispatch algorithm. – Holger Jun 01 '17 at 08:28