jvm reordering/visibility effect test

2019-05-01 03:33发布

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?

4条回答
对你真心纯属浪费
2楼-- · 2019-05-01 03:47

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.

查看更多
放荡不羁爱自由
3楼-- · 2019-05-01 03:55

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.

查看更多
虎瘦雄心在
4楼-- · 2019-05-01 03:55

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.

查看更多
迷人小祖宗
5楼-- · 2019-05-01 04:05

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.

查看更多
登录 后发表回答