美文网首页
Java Memory Model

Java Memory Model

作者: yunpxu | 来源:发表于2019-03-13 14:00 被阅读0次

    The Java Memory Model defines how threads interact through memory.

    All source code is available at github.

    Computer Memory Model

    sh-4.2# lscpu | grep 'CPU(s)\|cache'

    CPU(s):                2
    On-line CPU(s) list:   0,1
    L1d cache:             32K
    L1i cache:             32K
    L2 cache:              256K
    L3 cache:              4096K
    

    sh-4.2# cat /proc/meminfo | grep MemTotal

    MemTotal:        2047036 kB
    
    Computer Memory Model.png
    • 2 threads(1 thread per cpu) may run concurrently.
    • Data is read from RAM to Cache, then processed by CPU, then written(flushed) to the RAM.
    • The CPU Optimizer determines when to flush.
    • Data in the Cache is NOT visible to other threads.
    • Data in the RAM is visible to all threads, threads can communicate through RAM.

    JMM goals

    JMM need to ensure visibility, ordering and atomicity regardless of the underlying hardware memory model.
    A basic understanding of java concurrent stress test(JCST) is required.
    You can check the sample code from github.

    Visibility

    Write by one thread to a shared object may be invisible to another thread because of the existence cache.

    Code

    • Thread actor1 keeps reading from shared object (v).
    • Thread actor1 exits while v = 1 (changes from Thread signal is visible).
    • Thread signal is started after Thread actor1.
    • Thread signal write(v = 1) to shared object.
    @Outcome(id = "TERMINATED", expect = Expect.ACCEPTABLE, desc = "write in signal is visible to actor1.")
    @Outcome(id = "STALE", expect = Expect.ACCEPTABLE_INTERESTING, desc = "write in signal is NOT visible to actor1.")
    public class VisibilityTest {
    
        @JCStressTest(Mode.Termination)
        @JCStressMeta(VisibilityTest.class)
        @State
        public static class PlainReadWrite {
            int v;
    
            @Actor
            public void actor1() {
                while (v == 0) {
                    // spin
                }
            }
    
            @Signal
            public void signal() {
                v = 1;
            }
        }
    
        @JCStressTest(Mode.Termination)
        @JCStressMeta(VisibilityTest.class)
        @State
        public static class VolatileReadWrite {
            volatile int v;
    
            @Actor
            public void actor1() {
                while (v == 0) {
                    // spin
                }
            }
    
            @Signal
            public void signal() {
                v = 1;
            }
        }
    
        @JCStressTest(Mode.Termination)
        @JCStressMeta(VisibilityTest.class)
        @State
        public static class SynchronizedReadWrite {
            int v;
    
            @Actor
            public void actor1() {
                while (v == 0) {
                    synchronized (this) {
                        if (v == 0) {
                            continue;//release lock
                        }
                    }
                }
            }
    
            @Signal
            public void signal() {
                synchronized (this) {
                    v = 1;
                }
            }
        }
    }
    

    Result

    • JCST TERMINATED state means write in Thread signal is visible to Thread actor1.
    • JCST STALE state means write in Thread signal is NOT visible to Thread actor1.
    visibility test result.png

    Conclusion

    1. PlainReadWrite
      • Visibility of plain access is NOT ensured.
    2. VolatileReadWrite
      • volatile ensures visibility of shared object.
    3. SynchronizedReadWrite
      • synchronized ensures visibility of shared object.

    Ordering

    1. The compiler is free to reorder certain instructions as an optimization when it would not change the semantics of the program.
    2. The processor is allowed to execute operations out of order under some circumstances.
    3. The cache is generally allowed to write variables back to main memory in a different order than they were written by the program.

    Code

    • Thread actor1 write to i = 1; iSet = true;.
    • Thread actor2 read from r.r1 = iSet; r.r2 = i;.
    @Outcome(id = "false, 0", expect = ACCEPTABLE, desc = "actor2 reads before actor1.")
    @Outcome(id = "true, 1", expect = ACCEPTABLE, desc = "actor2 reads after actor1.")
    @Outcome(id = "false, 1", expect = ACCEPTABLE, desc = "actor1 write i, actor2 read iSet, actor2 read i, actor1 write iSet")
    @Outcome(id = "true, 0", expect = ACCEPTABLE_INTERESTING, desc = "REORDERING, actor1 write iSet, actor2 read iSet, actor2 read i, actor1 write i")
    public class OrderingTest {
    
        @JCStressTest
        @JCStressMeta(OrderingTest.class)
        @State
        public static class PlainOrdering {
            int i;
            boolean iSet;
    
            @Actor
            public void actor1() {
                i = 1;
                iSet = true;
            }
    
            @Actor
            public void actor2(ZI_Result r) {
                r.r1 = iSet;
                r.r2 = i;
            }
        }
    
        @JCStressTest
        @JCStressMeta(OrderingTest.class)
        @State
        public static class VolatileOrdering {
            int i;
            volatile boolean iSet;
    
            @Actor
            public void actor1() {
                i = 1;
                iSet = true;
            }
    
            @Actor
            public void actor2(ZI_Result r) {
                r.r1 = iSet;
                r.r2 = i;
            }
        }
    
        @JCStressTest
        @JCStressMeta(OrderingTest.class)
        @State
        public static class SynchronizedOrdering {
            int i;
            boolean iSet;
    
            @Actor
            public void actor1() {
                synchronized (this) {
                    i = 1;
                    iSet = true;
                }
            }
    
            @Actor
            public void actor2(ZI_Result r) {
                synchronized (this) {
                    r.r1 = iSet;
                    r.r2 = i;
                }
            }
        }
    }
    

    Result

    Execution Order.png
    • JCST (false, 0) state, read write is executed as case 1.
    • JCST (false, 1) state, read write is executed as case 2.
    • JCST (true, 0) state, read write is executed as case 4/5(reordering observed).
    • JCST (true, 1) state, read write is executed as case 3.
    visibility test result.png

    Conclusion

    1. PlainOrdering
      • Ordering of plain write and read is NOT ensured.
    2. VolatileOrdering
      • volatile ensures ordering of write and read.
    3. SynchronizedOrdering
      • synchronized ensures ordering of write and read.

    Atomicity

    An operation is atomic,
    A set of operations is atomic,

    Code

    public class AtomicityTest {
    
        @JCStressTest
        @Outcome(id = "0", expect = Expect.ACCEPTABLE, desc = "See initial value while writer not finished.")
        @Outcome(id = "-1", expect = Expect.ACCEPTABLE, desc = "See full value while writer finished")
        @Outcome(expect = Expect.FORBIDDEN, desc = "partial values are forbidden.")
        @State
        public static class IntegerAtomicity {
            int v;
    
            @Actor
            public void writer() {
                v = 0xFFFFFFFF;
            }
    
    
            @Actor
            public void reader(I_Result r) {
                r.r1 = v;
    
            }
        }
    
        @JCStressTest
        @Outcome(id = "0", expect = Expect.ACCEPTABLE, desc = "See initial value while writer not finished.")
        @Outcome(id = "65535", expect = Expect.ACCEPTABLE, desc = "See full value while writer0 finished.")
        @Outcome(id = "-65536", expect = Expect.ACCEPTABLE, desc = "See full value while writer1 finished.")
        @Outcome(expect = Expect.FORBIDDEN, desc = "Partial values are forbidden even in case of concurrent update.")
        @State
        public static class IntegerConcurrentAtomicity {
            int v;
    
            @Actor
            public void writer0() {
                v = 0x0000FFFF;
            }
    
            @Actor
            public void writer1() {
                v = 0xFFFF0000;
            }
    
    
            @Actor
            public void reader(I_Result r) {
                r.r1 = v;
    
            }
        }
    
        @JCStressTest
        @Outcome(id = "0", expect = Expect.ACCEPTABLE, desc = "See initial value while writer not finished.")
        @Outcome(id = "-1", expect = Expect.ACCEPTABLE, desc = "See full value while writer finished.")
        @Outcome(expect = Expect.ACCEPTABLE_INTERESTING, desc = "Partial values violate access atomicity, but allowed under JLS.")
        @State
        public static class LongAtomicity {
            long v;
    
            @Actor
            public void writer() {
                v = 0xFFFFFFFF_FFFFFFFFL;
            }
    
            @Actor
            public void reader(J_Result r) {
                r.r1 = v;
            }
        }
    
        @JCStressTest
        @Outcome(id = "0", expect = Expect.ACCEPTABLE, desc = "See initial value while writer not finished.")
        @Outcome(id = "-1", expect = Expect.ACCEPTABLE, desc = "See full value while writer finished.")
        @Outcome(expect = Expect.FORBIDDEN, desc = "Partial values are forbidden.")
        @State
        public static class VolatileLongAtomicity {
            volatile long v;
    
            @Actor
            public void writer() {
                v = 0xFFFFFFFF_FFFFFFFFL;
            }
    
            @Actor
            public void reader(J_Result r) {
                r.r1 = v;
            }
        }
    
    
        @JCStressTest
        @Outcome(id = "1", expect = Expect.ACCEPTABLE_INTERESTING, desc = "One update lost.")
        @Outcome(id = "2", expect = Expect.ACCEPTABLE, desc = "Both updates.")
        @State
        public static class PlainIncrement {
            int v;
    
            @Actor
            public void actor1() {
                v++;
            }
    
            @Actor
            public void actor2() {
                v++;
            }
    
            @Arbiter
            public void arbiter(I_Result r) {
                r.r1 = v;
            }
        }
    
    
        @JCStressTest
        @Outcome(id = "1", expect = Expect.ACCEPTABLE_INTERESTING, desc = "One update lost.")
        @Outcome(id = "2", expect = Expect.ACCEPTABLE, desc = "Both updates.")
        @State
        public static class VolatileIncrement {
            volatile int v;
    
            @Actor
            public void actor1() {
                v++;
            }
    
            @Actor
            public void actor2() {
                v++;
            }
    
            @Arbiter
            public void arbiter(I_Result r) {
                r.r1 = v;
            }
        }
    
        @JCStressTest
        @Outcome(id = "1", expect = Expect.FORBIDDEN, desc = "One update lost.")
        @Outcome(id = "2", expect = Expect.ACCEPTABLE, desc = "Both updates.")
        @State
        public static class SynchronizedIncrement {
            int v;
    
            @Actor
            public void actor1() {
                synchronized (this) {
                    v++;
                }
            }
    
            @Actor
            public void actor2() {
                synchronized (this) {
                    v++;
                }
            }
    
            @Arbiter
            public void arbiter(I_Result r) {
                r.r1 = v;
            }
        }
    
        @JCStressTest
        @Outcome(id = "1", expect = Expect.ACCEPTABLE_INTERESTING, desc = "One update lost.")
        @Outcome(id = "2", expect = Expect.ACCEPTABLE, desc = "Both updates.")
        @State
        public static class AtomicIntegerIncrement {
            AtomicInteger v = new AtomicInteger();
    
            @Actor
            public void actor1() {
                v.getAndIncrement();
            }
    
            @Actor
            public void actor2() {
                v.getAndIncrement();
            }
    
            @Arbiter
            public void arbiter(I_Result r) {
                r.r1 = v.get();
            }
        }
    
        @JCStressTest
        @Outcome(id = "100, 0", expect = Expect.ACCEPTABLE, desc = "Transfer not start yet.")
        @Outcome(id = "0, 100", expect = Expect.ACCEPTABLE, desc = "Transfer completed.")
        @Outcome(id = "0, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "See partial result.")
        @Outcome(id = "100, 100", expect = Expect.ACCEPTABLE_INTERESTING, desc = "See partial result.")
        @State
        public static class PlainTransfer {
            int a = 100;
            int b;
    
            @Actor
            public void actor1() {
                a = a - 100;
                b = b + 100;
            }
    
            @Actor
            public void arbiter(II_Result r) {
                r.r1 = a;
                r.r2 = b;
            }
        }
    
        @JCStressTest
        @Outcome(id = "100, 0", expect = Expect.ACCEPTABLE, desc = "Transfer not start yet.")
        @Outcome(id = "0, 100", expect = Expect.ACCEPTABLE, desc = "Transfer completed.")
        @Outcome(expect = Expect.FORBIDDEN, desc = "Forbidden case.")
        @State
        public static class SynchronizedTransfer {
            int a = 100;
            int b;
    
            @Actor
            public void actor1() {
                synchronized (this) {
                    a = a - 100;
                    b = b + 100;
                }
            }
    
            @Actor
            public void arbiter(II_Result r) {
                synchronized (this) {
                    r.r1 = a;
                    r.r2 = b;
                }
            }
        }
    }
    

    Result

    • Integer Atomicity
    Integer Atomicity.png
    • Long Atomicity
    Long Atomicity.png
    • Increment Atomicity
    Increment Atomicity.png
    • Transfer Atomicity
    Transfer Atomicity.png

    Conclusion

    • Read/Write of primitive types (boolean, byte, char, short, int, float, double, long) is atomic.
      Concurrent update of primitive types is safe.

    • Read/Write of long/double in 32 bits machine is not atomic.
      R/W to long/double can be divided into R/W high 32bits and low 32bits.
      volatile long/double ensures atomicity jls.

    • Increment(i++) is not atomic.
      i++ can be divided to (load i, i+1, store i).
      volatile doesn't ensure atomicity.
      java.util.concurrent.atomic.AtomicInteger ensures atomicity.
      synchronized ensures atomicity.

    • Transfer $100 from A to B is not atomic.
      This can be divided to (A-100, B+100).
      synchronized (A-100, B+100) ensures atomicity.

    Memory Barrier

    Types of memory barrier

    Memory Barrier.png

    JSR-133 ordering rules

    JSR-133 ordering rules.png
    • Volatile Load can't be reordered with subsequent operations.
    • Monitor Enter can't be reordered with subsequent operations.
    • Volatile Store can't be reordered with precedent operations.
    • Monitor Exit can't be reordered with precedent operations.
    • Volatile Store can't be reordered with subsequent Volatile Load and Monitor Enter.
    • Monitor Exit can't be reordered with subsequent Volatile Load and Monitor Enter.

    volatile

    volatile memory barrier.png

    synchronized

    synchronized memory barrier.png

    final

    final memory barrier.png

    Reference

    Wiki java memory model
    The Java Memory Model by Doug Lea
    JLS-17
    JSR-133
    Fixing the Java Memory Model, Part 1
    Fixing the Java Memory Model, Part 2
    tutorials jenkov java-memory-model
    深入理解Java内存模型

    相关文章

      网友评论

          本文标题:Java Memory Model

          本文链接:https://www.haomeiwen.com/subject/hvkurqtx.html