JUC 整理笔记五之梳理 Varhandle(下)

用户头像
JFound
关注
发布于: 2020 年 06 月 03 日

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



结合前面所学习的 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 的值来达到让循环终止的目的,其测试结果 STALE、*TERMINATED* 并行存在, 说明在测试案例中,是有存在修改 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



您的关注,是我最大的动力



发布于: 2020 年 06 月 03 日 阅读数: 34
用户头像

JFound

关注

梳理java知识,发现Java相关的更多领域知识 2020.05.19 加入

Java技术栈奋斗者

评论

发布
暂无评论
JUC整理笔记五之梳理Varhandle(下)