zoukankan      html  css  js  c++  java
  • JUC整理笔记五之梳理Varhandle(下)

    前文综合描述了 Varhandle 以及 Varhandle 能够做的事情,但是要了解并使用 Varhandle 并非是一件容易的事。总的来说,要想很好地使用 Varhandle ,必须先了解plain(普通方式)opaquerelease/acquirevolatile 的区别及使用。

    结合前面所学习的 jcstress ,本文用 jcsstress 作为并发测试工具来结合一些例子说明 plain、opaque、release/acqiure、volatile的特性。

    如果不知道 jcstress 的使用的话,可以参考下 JUC整理笔记三之测试工具jcstress

    内存可见性的区别

    普通变量

    @JCStressTest(Mode.Termination)
    @Outcome(id = "STALE", expect = Expect.ACCEPTABLE)
    @Outcome(id = "TERMINATED", expect = Expect.ACCEPTABLE)
    public static class PlainTester {
        private int x = 0;
        @Actor
        public void actor() {
            while (x == 0) {
                //do nothing
            }
        }
        @Signal
        public void signal() {
            x = 1;
        }
    }
    

    上面测试案例是有一个 while 循环,然后另外一个线程修改 x 的值来达到让循环终止的目的,其测试结果 STALETERMINATED 并行存在, 说明在测试案例中,是有存在修改 x 值后,循环是没有结束的案例。

    结果表明,多线程的环境中,普通变量是无法保证线程内存可见性的

    opaque

    @JCStressTest(Mode.Termination)
    @Outcome(id = "STALE", expect = Expect.FORBIDDEN)
    @Outcome(id = "TERMINATED", expect = Expect.ACCEPTABLE)
    public static class OpaqueTester {
        private volatile int x = 0;
        private static final VarHandle X; d
        static {
            try {
                X = MethodHandles.lookup().findVarHandle(OpaqueTester.class, "x", int.class);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new Error(e);
            }
        }
    
        @Actor
        public void actor() {
            while ((int) X.getOpaque(this) == 0) {
                //do nothing
            }
        }
        @Signal
        public void signal() {
            X.setOpaque(this, 1);
        }
    }
    

    这里使用了 Varhanle 来测试 opaque 的功能,测试的 Outcome 是不存在 STALE

    结果表明,多线程环境中, opaque 是可以保存内存可见性的

    release/acquire

    @JCStressTest(Mode.Termination)
    @Outcome(id = "STALE", expect = Expect.FORBIDDEN)
    @Outcome(id = "TERMINATED", expect = Expect.ACCEPTABLE)
    public static class ReleaseAcquireTester {
        private volatile int x = 0;
        private static final VarHandle X;
        static {
            try {
                X = MethodHandles.lookup().findVarHandle(ReleaseAcquireTester.class, "x", int.class);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new Error(e);
            }
        }
        @Actor
        public void actor() {
            while ((int) X.getAcquire(this) == 0) {
                //do nothing
            }
        }
        @Signal
        public void signal() {
            X.setRelease(this, 1);
        }
    }
    

    该例子和 opaque 一样,也是不存在 STALE

    结果表明,多线程环境下,release/acquire 是可以保证内存可见性的

    volatile

    @JCStressTest(Mode.Termination)
    @Outcome(id = "STALE", expect = Expect.FORBIDDEN)
    @Outcome(id = "TERMINATED", expect = Expect.ACCEPTABLE)
    public static class Volatile2Tester {
        private int x = 0;
        private static final VarHandle X;
        static {
            try {
                X = MethodHandles.lookup().findVarHandle(Volatile2Tester.class, "x", int.class);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new Error(e);
            }
        }
        @Actor
        public void actor() {
            while ((int) X.getVolatile(this) == 0) {
                //do nothing
            }
        }
        @Signal
        public void signal() {
            X.setVolatile(this, 1);
        }
    }
    

    这里的 volatile 是借助 Varhandle 来测试,用法和在变量前加 volatile 关键字是一样的。

    和前面的两个例子一样,也是不存在 STALE

    结果表明,多线程环境下 volatile 是可以保证内存可见性的

    小结

    普通变量是不保证内存可见性的,opaque 、 release/acquire 、volatile 是可以保证内存可见性。

    特性区别

    在测试之前,先准备下测试实体类

    package jfound;
    import java.lang.invoke.MethodHandles;
    import java.lang.invoke.VarHandle;
    public class TestData {
        public int x;
        public int y;
        public static final VarHandle X;
        public static final VarHandle Y;
        static {
            try {
                X = MethodHandles.lookup().findVarHandle(TestData.class, "x", int.class);
                Y = MethodHandles.lookup().findVarHandle(TestData.class, "x", int.class);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new Error(e);
            }
        }
    }
    

    普通变量

    @JCStressTest
    @Outcome(id = "0, 0", expect = Expect.ACCEPTABLE)
    @Outcome(id = "1, 0", expect = Expect.ACCEPTABLE)
    @Outcome(id = "0, 1", expect = Expect.ACCEPTABLE)
    @Outcome(id = "1, 1", expect = Expect.ACCEPTABLE)
    @State
    public static class PlainOrderTester {
        private final TestData testData = new TestData();
        @Actor
        public void actor1(II_Result r) {
            testData.x = 1;
            r.r2 = testData.y;
        }
        @Actor
        public void actor2(II_Result r) {
            testData.y = 1;
            r.r1 = testData.x;
        }
    }
    

    上面的测试案例中,存在 “ 0, 0 ” 这个结果,说明代码中出现了重排序。

    opaque

    Varhandle 中对 getOpaque 、 setOpaque 的说明是,按程序顺序执行,不确保其他线程可见顺序

    • 测试一
    /*
     * 不确保顺序,存在 0, 0 这结果
     */
    @JCStressTest
    @Outcome(id = "0, 0", expect = Expect.ACCEPTABLE)
    @Outcome(id = "1, 0", expect = Expect.ACCEPTABLE)
    @Outcome(id = "0, 1", expect = Expect.ACCEPTABLE)
    @Outcome(id = "1, 1", expect = Expect.ACCEPTABLE)
    @State
    public static class OpaqueOrderTester1 {
        private final TestData testData = new TestData();
        @Actor
        public void actor1(II_Result r) {
            TestData.X.setOpaque(testData, 1);
            r.r2 = (int) TestData.Y.getOpaque(testData);
        }
        @Actor
        public void actor2(II_Result r) {
            TestData.Y.setOpaque(testData, 1);
            r.r1 = (int) TestData.X.getOpaque(testData);
        }
    }
    /**
     * 不确保顺序,存在 1, 1 这结果
     */
    @JCStressTest
    @Outcome(id = "0, 0", expect = Expect.ACCEPTABLE)
    @Outcome(id = "1, 0", expect = Expect.ACCEPTABLE)
    @Outcome(id = "0, 1", expect = Expect.ACCEPTABLE)
    @Outcome(id = "1, 1", expect = Expect.ACCEPTABLE)
    @State
    public static class OpaqueOrderTester2 {
        private final TestData testData = new TestData();
        @Actor
        public void actor1(II_Result r) {
            r.r2 = (int) TestData.Y.getOpaque(testData);
            TestData.X.setOpaque(testData, 1);
        }
        @Actor
        public void actor2(II_Result r) {
            r.r1 = (int) TestData.X.getOpaque(testData);
            TestData.Y.setOpaque(testData, 1);
        }
    }
    

    结论: 结合前面 opaque 是保证可见性的结论,根据上面的例子可以得出,opaque不保证其它线程的可见顺序

    • 测试二
    /**
     * 不存在 1, 0 ,相对普通变量来说,也是按顺序执行
     */
    @JCStressTest
    @Outcome(id = "1, 1", expect = ACCEPTABLE)
    @Outcome(id = "1, 0", expect = FORBIDDEN)
    @Outcome(id = "0, 1", expect = ACCEPTABLE)
    @Outcome(id = "0, 0", expect = ACCEPTABLE)
    @State
    public static class OpaqueOrderTester3 {
        private final TestData testData = new TestData();
        @Actor
        public void actor1() {
            testData.y = 1;
            TestData.X.setOpaque(testData, 1);
        }
        @Actor
        public void actor2(II_Result r) {
            r.r1 = (int) TestData.X.getOpaque(testData);
            r.r2 = testData.y;
        }
    }
    
    /**
     * 不存在 1, 0 ,说明 setOpaque 和 getOpaque 都是按顺序执行的
     */
    @JCStressTest
    @Outcome(id = "1, 1", expect = ACCEPTABLE)
    @Outcome(id = "1, 0", expect = FORBIDDEN)
    @Outcome(id = "0, 1", expect = ACCEPTABLE)
    @Outcome(id = "0, 0", expect = ACCEPTABLE)
    @State
    public static class OpaqueOrderTester4 {
        private final TestData testData = new TestData();
        @Actor
        public void actor1() {
            TestData.Y.setOpaque(testData, 1);
            TestData.X.setOpaque(testData, 1);
        }
        @Actor
        public void actor2(II_Result r) {
            r.r1 = (int) TestData.X.getOpaque(testData);
            r.r2 = (int) TestData.Y.getOpaque(testData);
        }
    }
    

    结论:上面两个例子都是没有 1, 0 这个例子,说明在 loadstore 情况下, opaque都是能够保证执行顺序的。

    release/acquire

    • 测试一
    @JCStressTest
    @Outcome(id = "1, 1", expect = ACCEPTABLE)
    @Outcome(id = "1, 0", expect = FORBIDDEN)
    @Outcome(id = "0, 1", expect = ACCEPTABLE)
    @Outcome(id = "0, 0", expect = ACCEPTABLE)
    @State
    public static class RAOrderTester1 {
        private final TestData testData = new TestData();
        @Actor
        public void actor1() {
            testData.y = 1;
            TestData.X.setRelease(testData, 1);
        }
        @Actor
        public void actor2(II_Result r) {
            r.r1 = (int) TestData.X.getAcquire(testData);
            r.r2 = testData.y;
        }
    }
    

    结论:不存在1, 0 这个结果,说明是 release/acquire 是可以按顺序执行的

    • 测试二
    /**
     * 不存在1, 1,不存在重排序
     */
    @JCStressTest
    @Outcome(id = "0, 0", expect = Expect.ACCEPTABLE)
    @Outcome(id = "1, 0", expect = Expect.ACCEPTABLE)
    @Outcome(id = "0, 1", expect = Expect.ACCEPTABLE)
    @Outcome(id = "1, 1", expect = Expect.FORBIDDEN)
    @State
    public static class RAOrderTester2 {
        private final TestData testData = new TestData();
        @Actor
        public void actor1(II_Result r) {
            r.r2 = (int) TestData.Y.getAcquire(testData);
            TestData.X.setRelease(testData, 1);
        }
        @Actor
        public void actor2(II_Result r) {
            r.r1 = (int) TestData.X.getAcquire(testData);
            TestData.Y.setRelease(testData, 1);
        }
    }
    
    /**
     * 出现 0, 0 则表示被重排序
     */
    @JCStressTest
    @Outcome(id = "0, 0", expect = Expect.ACCEPTABLE)
    @Outcome(id = "1, 0", expect = Expect.ACCEPTABLE)
    @Outcome(id = "0, 1", expect = Expect.ACCEPTABLE)
    @Outcome(id = "1, 1", expect = Expect.ACCEPTABLE)
    @State
    public static class RAOrderTester3 {
        private final TestData testData = new TestData();
        @Actor
        public void actor1(II_Result r) {
            TestData.X.setRelease(testData, 1);
            r.r2 = (int) TestData.Y.getAcquire(testData);
        }
        @Actor
        public void actor2(II_Result r) {
            TestData.Y.setRelease(testData, 1);
            r.r1 = (int) TestData.X.getAcquire(testData);
        }
    }
    

    结论:setRelease 确保前面的load和store不会被重排序到后面,但不确保后面的load和store重排序到前面;getAcquire 确保后面的load和store不会被重排序到前面,但不确保前面的load和store被重排序。

    volatile

    • 测试
     /**
      * 没有出现 0, 0
      */
     @JCStressTest
     @Outcome(id = "1, 1", expect = ACCEPTABLE)
     @Outcome(id = "1, 0", expect = ACCEPTABLE)
     @Outcome(id = "0, 1", expect = ACCEPTABLE)
     @State
     public static class VolatileTester1 {
    
         private final TestData testData = new TestData();
    
    
         @Actor
         public void actor1(II_Result r) {
             TestData.X.setVolatile(testData, 1);
             r.r2 = (int) TestData.Y.getVolatile(testData);
         }
    
         @Actor
         public void actor2(II_Result r) {
             TestData.Y.setVolatile(testData, 1);
             r.r1 = (int) TestData.X.getVolatile(testData);
         }
     }
    
    
     /**
      * 没有出现 1, 0
      */
     @JCStressTest
     @Outcome(id = "1, 1", expect = ACCEPTABLE)
     @Outcome(id = "1, 0", expect = FORBIDDEN)
     @Outcome(id = "0, 1", expect = ACCEPTABLE)
     @Outcome(id = "0, 0", expect = ACCEPTABLE)
     @State
     public static class VolatileTester2 {
    
         private final TestData testData = new TestData();
    
         @Actor
         public void actor1() {
             testData.y = 1;
             TestData.X.setVolatile(testData, 1);
         }
    
         @Actor
         public void actor2(II_Result r) {
             r.r1 = (int) TestData.X.getVolatile(testData);
             r.r2 = testData.y;
         }
     }
    

    结论: volatile 之间操作是能够确保顺序的,能保证变量之间的不被重排序

    总结

    前面的几个例子,说明了普通变量、opaque、release/acquire、volatile之间的区别

    • 普通变量是不确保内存可见的,opaque、release/acquire、volatile是可以保证内存可见的
    • opaque 确保程序执行顺序,但不保证其它线程的可见顺序
    • release/acquire 保证程序执行顺序,setRelease 确保前面的load和store不会被重排序到后面,但不确保后面的load和store重排序到前面;getAcquire 确保后面的load和store不会被重排序到前面,但不确保前面的load和store被重排序。
    • volatile确保程序执行顺序,能保证变量之间的不被重排序。

    代码地址:https://github.com/jfound/varhandle

  • 相关阅读:
    动态生成 Excel 文件供浏览器下载的注意事项
    JavaEE 中无用技术之 JNDI
    CSDN 泄露用户密码给我们什么启示
    刚发布新的 web 单点登录系统,欢迎下载试用,欢迎提建议
    jQuery jqgrid 对含特殊字符 json 数据的 Java 处理方法
    一个 SQL 同时验证帐号是否存在、密码是否正确
    PostgreSQL 数据库在 Windows Server 2008 上安装注意事项
    快速点评 Spring Struts Hibernate
    Apache NIO 框架 Mina 使用中出现 too many open files 问题的解决办法
    解决 jQuery 版本升级过程中出现 toLowerCase 错误 更改 doctype
  • 原文地址:https://www.cnblogs.com/jfound/p/13039629.html
Copyright © 2011-2022 走看看