5

While writing some java article I'm trying to reproduce re-ordering in case of unsynchronized object costruction in multithreaded environment. The case when a heavy object is constructed w/o synchonization/volatiles/finals and other threads get access to it right after constructor call. Here is the code I try:

public class ReorderingTest {
    static SomeObject<JPanel>[] sharedArray = new SomeObject[100];

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            String name = "watcher" + i;
            new Thread(new Watcher(name)).start();
            System.out.printf("watcher %s started!%n", name);
        }
    }

    static class Watcher implements Runnable {
        private String name;

        Watcher(String name) {
            this.name = name;
        }

        public void run() {
            while (true) {
                int randomIndex = (int) (Math.random() * sharedArray.length);
                SomeObject<JPanel> item = sharedArray[randomIndex];
                if (item == null) {
                    //System.out.printf("sharedArray[%s]=null%n", randomIndex);
                    double r = 1 + Math.random() * 1000;
                    sharedArray[randomIndex] = new SomeObject<JPanel>(
                            new JPanel(), UUID.randomUUID().toString(), r, (float)r * 33, (long)r);
                } else {
                    //System.out.printf("sharedArray[%s]=<obj>!%n", randomIndex);
                    if (item.value == null ||
                            (item.stringField == null) ||
                            (item.doubleField == 0) ||
                            (item.floatField == 0) ||
                            (item.longField == 0)
                            ) {
                        System.err.printf("watcher %s sees default values: %s!%n", name, item);
                    } else {
                        // fully initialized! run new construction process
                        double r = 1 + Math.random() * 1000;
                        sharedArray[randomIndex] = new SomeObject<JPanel>(
                                new JPanel(), UUID.randomUUID().toString(), r, (float)r * 37, (long)r);

                    }
                }
                /*try {
                    TimeUnit.NANOSECONDS.sleep(randomIndex);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }*/
            }
        }
    }

    static class SomeObject<V> {
        V value;
        String stringField;
        double doubleField;
        float floatField;
        long longField;

        SomeObject(V value, String stringField, double doubleField, float floatField, long longField) {
            this.value = value;
            this.stringField = stringField;
            this.doubleField = doubleField;
            this.floatField = floatField;
            this.longField = longField;
        }

        @Override
        public String toString() {
            return "SomeObject{" +
                    "value=" + value == null ? "null" : "<obj>" +
                    ", stringField='" + stringField + '\'' +
                    ", doubleField=" + doubleField +
                    ", floatField=" + floatField +
                    ", longField=" + longField +
                    '}';
        }
    }
} 

-But no effect so far, I've tried on different 2,4 and 8 core Intel/AMD PCs with windows, ran test for a few hours - no reordering effect - System.err.printf("watcher %s sees ...") - is not called, static sharedArray[randomIndex] reference always contains fully constructed values.

What's wrong? How to reproduce this?

yetanothercoder
  • 1,689
  • 4
  • 21
  • 43
  • it's _very_ hard to reproduce deliberately. you generally need your code to be running on multiple cpus and to be considered "hot" by hoptspot so that it is aggressively optimized by the jit. and even then, it's still hard to reproduce in testing. – jtahlborn Jun 07 '11 at 11:46
  • accoring to [jmm](http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#reordering): "There are a number of potential sources of reordering, such as the compiler, the JIT, and the cache" - there are 2 other possibilities of this effect: the compiler and the cache (hardware)... but seems they are also very rare in home conditions. – yetanothercoder Jun 07 '11 at 12:30
  • See also https://stackoverflow.com/questions/12159/how-should-i-unit-test-threaded-code – Raedwald Nov 12 '17 at 10:23

4 Answers4

1

Re-ordering is not guaranteed. It usually only occurs when the JIT determines there might be a performance gain.

In your case, what you are looking for is not re-ordering but an Object which doesn't appear to have been correctly initialised.

Your objects are highly likely to use a new memory location each time (or at least one which is not in cache) On x86/x64 architectures, I have found that the cache will always be correct the first time it has to load memory when was updated by another thread.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • why "objects are highly likely to use a new memory location each time" - because they are large? Do you mean there is some feature in ccNuma architecture on PC that ensures correct initialization of objects in jvm w/o safe publishing? – yetanothercoder Jun 07 '11 at 11:54
  • Objects are allocated in Eden space. If your Eden space was small enough to fit in the cache, you could have old data in one thread's cache after creating a new object in another thread. However, since each thread is creating lots of objects, they ensure that any old data is pushed out. – Peter Lawrey Jun 07 '11 at 11:57
  • So, only creating small objects rarely and having small Eden size - can reproduce this? – yetanothercoder Jun 07 '11 at 12:08
  • If you have only as many threads as you have cores, and a some thread only checking the objects, you are more likely to see a problem. – Peter Lawrey Jun 07 '11 at 12:41
1

re-ordering does not necessarily happen. and when it does, it's hard to observe. you sample size of 100 is too tiny. Try a billion for starters.

Suppose re-ordering happens, at the time of write, reference is assigned first, then fields are populated. Your read process reads the reference first, then reads the fields. So write and read follow the same order, it's almost impossible for read to catch up and get ahead of write.

You can address the issue by

  1. pause in constructor (therefore pause in write)
  2. write and read fields in opposite direction

Still, reordering may not occur and you won't be able to observe it.

irreputable
  • 44,725
  • 9
  • 65
  • 93
  • thanks, tried. Pauses in constructor (`TimeUnit.NANOSECONDS.sleep((long)Math.random() * 1000)`) + smaller sharedArray size [10] + more threads (3000, jmv crashes with OOM if more) - didn't help, no change. – yetanothercoder Jun 07 '11 at 13:18
  • It's more likely that fields are alway assigned before reference is addigned, but other CPUs see it on different order due to e.g.: banked caches, as on some Alpha machines. – ninjalj Jun 11 '11 at 08:41
1

Here is a nice article which should show reordering on x86 (quite a feat because the x86 memory model is pretty much "safe"):

http://bartoszmilewski.wordpress.com/2008/11/05/who-ordered-memory-fences-on-an-x86/

Your example will not show reordering. The compiler will not reorder to "store object reference after allocation but before construction" because the constructor could throw and thus the reference would need to be reverted. Some Processors may reorder this, but no intel compatible ones, due to the given guaranties.

Markus Kull
  • 1,471
  • 13
  • 16
  • thanks, seems most reasonable. But they show it in [jmm-faq `FinalFieldExample`](http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#finalWrong) as an example of visibility issue (reordering) with non-final fields. – yetanothercoder Jun 07 '11 at 18:04
  • btw, tested on Solaris sparc (SunOS 5.10 sun4us sparc) - no change either, seems such primitive reordering is prohibited here too. – yetanothercoder Jun 08 '11 at 08:30
  • wrong. see http://stackoverflow.com/questions/16178020/uninitialized-object-leaked-to-another-thread-despite-no-code-explicitly-leaking or by x86 do you not include x64? – Dog Apr 23 '13 at 20:51
0

could you try to declare sharedArray as non-volatile? It seems to me that handing a volatile reference to a correctly constructed object is safe.

Rom1
  • 3,167
  • 2
  • 22
  • 39