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

原文地址:https://www.cnblogs.com/jfound/p/13039629.html