14

I'm trying to reproduce a memory visibility issue in case of insufficient object initialization for non-final fields (JLS 17.5 Final Field Semantics, FinalFieldExample class example). Where it stated "However, f.y is not final; the reader() method is therefore not guaranteed to see the value 4 for it"

I've tried this code:

public class ReorderingTest2 {


    public static void main(String[] args) {
        for (int i = 0; i < 2500; i++) {
            new Thread(new Reader(i)).start();
            new Thread(new Writer(i)).start();
        }
    }

    static class Reader implements Runnable {
        private String name;

        Reader(int i) {
            this.name = "reader" + i;
        }

        @Override
        public void run() {
            //System.out.println(name + " started");
            while (true) {
                FinalFieldExample.reader(name);
            }
        }
    }

    static class Writer implements Runnable {
        private String name;

        Writer(int i) {
            this.name = "writer" + i;
        }

        @Override
        public void run() {
            //System.out.println(name + " started");
            while (true) {
                FinalFieldExample.writer();
            }
        }
    }

    static class FinalFieldExample {
        int x;
        int y;
        static FinalFieldExample f;

        public FinalFieldExample() {
            x = 3;
            y = 4;
        }

        static void writer() {
            f = new FinalFieldExample();
        }

        static void reader(String name) {
            if (f != null) {
                int i = f.x;
                int j = f.y;
                if (i != 3 || j != 4) {
                    System.out.printf("reader %s sees it!%n", name);
                }
            }
        }
    }

}

As in previous my similar topic - I've tried on different PCs (from 2 to 8 cores) with Windows and even on our server-side Solaris 32 core box - I couldn't reproduce it: f.x and f.y - are always already proper-initialized.

For Intel/x86/x64 architecture as I got the answer - they have pretty much default memery guarantees which prevent such constructor logic reordering. Seems the same is true for Solaris/sparc too?

So in what architecture/OSes this reordering can be reproduced?

Community
  • 1
  • 1
yetanothercoder
  • 1,689
  • 4
  • 21
  • 43
  • btw you dont need so many threads (unless you do have the cores) and you have a datarace in reader. basically the reader can see 2 different instances of `f`. As for the question, I guess Azul can be a possible answer since it has the weakest model (x86 has one of the strongest). The other one could be anything running on IA-64. – bestsss Jun 09 '11 at 21:52
  • Edit: I dont know if the test is good since accessing 'f' will always hit the cache (the reference it's always the same location) but reading off it probably won't. Perhaps you'd like large object spanning more than a single cache line as well. – bestsss Jun 09 '11 at 21:59
  • I've tried 2 or 8 cycles instead of 2500 (you can try quickly) - no change. Data races don't matter, this null check is actually against NPE before first `f` assignment, then it just test whether object where `f` point to is fully initialized, no matter if it is new object or the same as in null check... And I tried large objects in the http://stackoverflow.com/questions/6264466/jvm-reordering-visibility-effect-test – yetanothercoder Jun 10 '11 at 05:32
  • 3
    the object there is still small try putting 16 longs and initialized them like 1,2,3... etc. to span more than a cache line. A cache line on x86 is 64 bytes, IA-64 is 32/64 bytes (Itanium2 is 64). – bestsss Jun 10 '11 at 07:59
  • [Here](http://stackoverflow.com/questions/16178020/uninitialized-object-leaked-to-another-thread-despite-no-code-explicitly-leaking) is a working example. I have it happening on openjdk 6 on x64 linux. – Dog Apr 23 '13 at 20:38

4 Answers4

1

Alpha. Paul E. McKenney's book Is Parallel Programming Hard, And, If So, What Can You Do About It? has a chapter explaining thememory model of the most important platforms.

ninjalj
  • 42,493
  • 9
  • 106
  • 148
0

To get the result you want, you might try to turn on heavy optimization, so run the program in -server mode.

I first thought of making f volatile, but that would obviously screw the whole experiment.

Turn on the XML logging for the just-in-time compiler (if you are using the HotSpot JVM), and look at the generated machine code (using some external debugger or memory dumper). Then you can examine the generated code if that would even allow you to observe the result you want.

Roland Illig
  • 40,703
  • 10
  • 88
  • 121
0

I suggest you acquire a copy of "Java Concurrency in Practice" and read Chapter 3 which details the JVM guarantees around locking and visibility. Your question has nothing to do with a specific architecture and everything to do with understanding happens-before in Java.

I think that you cannot repro the problem because there is a happens-before edge at the end of the FinalFieldExample constructor which is guaranteeing that x = 3 and y = 4;

BTW. The FinalFieldExample object is a bit of a mess. It wants to be a proper singleton, but you didn't code it that way. The lack of synchronization around the static "f" makes it more difficult than it should be to reason about the runtime behavior of this class. Methinks it should be a proper singleton with synchronization protecting access to the static "f" and you should be calling the writer and reader methods like...

FinalFieldExample.getInstance().writer();

Just sayin'

Bob Kuhar
  • 10,838
  • 11
  • 62
  • 115
  • 1
    *Your question has nothing to do with a specific architecture and everything to do with understanding happens-before in Java.* Yes, it does. No hardware model matches java one, so the question is: if breaking the described java model will ever break any available hardware memory model, and it's a valid question. – bestsss Jun 23 '11 at 21:45
  • 1
    An implementation can only call itself a JVM if it supports the JVM memory model. That is the whole purpose of the JVM, to provide an abstraction layer above the specific hardware. If the dude asking the question was to locate a JVM / hardware combination that operate in the way he prescribes, by definition that would not be a legitimate JVM. – Bob Kuhar Jun 23 '11 at 22:07
  • 3
    no he is not asking that... the question is mostly a hardware one, and it is something like, is there any combination of hardware and an optimization compiler that would produce invalid results. He is not asking if the JVM impl will break under proper use. – bestsss Jun 23 '11 at 22:10
  • 2
    _I think that you cannot repro the problem because there is a happens-before edge at the end of the FinalFieldExample constructor which is guaranteeing that x = 3 and y = 4;_ @Bob, no, there is NO happens-before in this case, see [jmm doc](http://java.sun.com/docs/books/jls/third_edition/html/memory.html) or JCiP for the details. – yetanothercoder Jun 24 '11 at 05:20
  • @yetanothercoderu - I read "*If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).* " as applying in this case, in that evaluation of the rhs precedes assignment in program order in the writer() assignment `f = new FinalFieldExample();`, per section 15.26.1 of the jls. What am I missing? – Ed Staub Jul 08 '11 at 18:08
  • 1
    >If x and y are actions of the same thread... - not the case, reader & writer are different threads. – yetanothercoder Jul 11 '11 at 11:01
  • 1
    Bob is right on this one. This code does not have "valid" and "invalid" results; it should be treated as code that can fail, with varying degrees of probability of failure on various platforms. It seems that on the Intel platform it fails rarely (and perhaps not at all), but that is not a guarantee. The author of the original question is asking whether or not it's safe to break the concurrency rules in a certain way, and he's wondering under what circumstances he'll get caught. Legitimate question, as long as he recognizes that he's impacting the future-proofing of his app. – Ross Judson Jul 23 '11 at 13:36
0

This should perhaps be a separate question... but it's very on-point. It's a more expansive version of a comment I made earlier.

The first part of section 17.4 of the jls says:

To determine if the actions of thread t in an execution are legal, we simply evaluate the implementation of thread t as it would be performed in a single threaded context, as defined in the rest of this specification.

The place I get hung up is understanding what "as defined in the rest of this specification" means, with regard to program order.

In the case at hand, the assignment

f = new FinalFieldExample();

is subject to assignment semantics (section 15.26.1), of which the following pertains. It is confusingly misformatted in the spec (the third step especially), I believe I have reformatted it to accurately reflect intent.

[Otherwise,] three steps are required:

  1. First, the left-hand operand is evaluated to produce a variable. If this evaluation completes abruptly, then the assignment expression completes abruptly for the same reason; the right-hand operand is not evaluated and no assignment occurs.
  2. Otherwise, the right-hand operand is evaluated. If this evaluation completes abruptly, then the assignment expression completes abruptly for the same reason and no assignment occurs.
  3. Otherwise, the value of the right-hand operand is converted to the type of the left-hand variable, is subjected to value set conversion (§5.1.13) to the appropriate standard value set (not an extended-exponent value set), and the result of the conversion is stored into the variable.

This reads like a specification of single-thread "program order" to me. What am I misinterpreting?

An answer, perhaps, is that what is really intended is a "duck test" - if a single thread executes as if everything were done in the order specified, it's a correct implementation. But this section is written very differently from other places where this is made clear by using the word appear, e.g.:

The Java programming language also guarantees that every operand of an operator (except the conditional operators &&, ||, and ? :) appears to be fully evaluated before any part of the operation itself is performed.

Ed Staub
  • 15,480
  • 3
  • 61
  • 91
  • _This reads like a specification of single-thread "program order" to me. What am I misinterpreting?_ there tons of threads here, so no sigle-threaded logic, or... I didn't get your point, could you explain more exactly? – yetanothercoder Jul 13 '11 at 04:55
  • 1
    Ok, on the nth reading, I think I see where I went wrong. I took `If x and y are actions of the same thread and x comes before y in program order, then hb(x, y). ` and assumed that it implied that another thread would see x before y. Totally wrong - right? – Ed Staub Jul 13 '11 at 11:52