文章目录
- 一、什么是VarHandle
- 0、JMM
- 1、jdk9之前无锁技术的实现
- 二、VarHandle使用
- 1、VarHandle 快速上手
- 2、VarHandle常用方法
- 3、实战案例1:解决可见性(比volatile轻量)
- 4、实战案例2:解决指令重排序(比volatile轻量)
- (1)案例分析: partial ordering
- (2)案例分析:total ordering
一、什么是VarHandle
0、JMM
JMM内存模型深入详解,探索volatile、synchronized与VarHandle深层次的奥秘
1、jdk9之前无锁技术的实现
JDK9之前, Java 中的无锁技术主要体现在以 AtomicInteger
为代表的的原子操作类,它的底层使用 Unsafe
实现,而Unsafe 的问题在于安全性和可移植性。
此外,volatile 主要使用了 Store-Load 屏障来控制顺序,这个屏障还是太强了,有没有更轻量级的解决方法呢?
二、VarHandle使用
1、VarHandle 快速上手
在 Java9 中引入了 VarHandle
,来提供更细粒度的内存屏障,保证共享变量读写可见性、有序性、原子性。提供了更好的安全性和可移植性,替代 Unsafe 的部分功能。
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class TestVarHandle {
int x; // 共享变量
static VarHandle X; // 共享变量的操作对象 VarHandle 的创建比较耗时,所以最好不要重复创建
static {
try {
// 初始化VarHandle :需要指定要保护的变量,某个类中的 共享变量名名 共享变量类型
X = MethodHandles.lookup()
.findVarHandle(TestVarHandle.class, "x", int.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TestVarHandle obj = new TestVarHandle();
X.set(obj, 10); // 设置值
Object o = X.get(obj); // 获取值
System.out.println(o);
}
}
2、VarHandle常用方法
3、实战案例1:解决可见性(比volatile轻量)
下面的实例,是一个经典案例,循环永远不会停止,具体原因此处就不分析了,就是因为可见性导致的。解决这个问题很简单,将stop用volatile进行标记就可以了。
// 永不终止的循环 - 可见性问题
public class TestInfinityLoop {
static boolean stop = false; //停止标记
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
stop = true; // volatile 的写
});
System.out.println("start " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
t.start();
foo();
}
private static void foo() {
while (true) {
boolean b = stop; // volatile 的读
if (b) {
break;
}
}
System.out.println("end " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
}
}
jdk9引入的VarHandle ,可以替换volatile,也可以实现共享变量的可见性问题:
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Date;
public class TestVarHandleOpaque {
static boolean stop;
static VarHandle STOP;
static {
try {
STOP = MethodHandles.lookup()
.findStaticVarHandle(TestVarHandleOpaque.class, "stop", boolean.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (true) {
if ((boolean) STOP.getOpaque()) {
break;
}
}
System.out.println("quit " + new Date());
}).start();
System.out.println("start " + new Date());
Thread.sleep(1000);
STOP.setOpaque(true);
}
}
4、实战案例2:解决指令重排序(比volatile轻量)
同样的,以下例子可以使用VarHandle解决指令重排序的问题:
import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.II_Result;
@JCStressTest
@State
// r1 r2
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE)
@Outcome(id = "0, 2", expect = Expect.ACCEPTABLE)
@Outcome(id = "1, 2", expect = Expect.ACCEPTABLE)
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE_INTERESTING)
public class Test4Possible {
int a;
int b;
@Actor // 线程1
public void action1(II_Result r) {
a = 1;
r.r2 = b;
}
@Actor // 线程2
public void action2(II_Result r) {
b = 2;
r.r1 = a;
}
}
public class TestVarHandle {
/**
* 案例1: 演示 VarHandle 可以用来解决 partial ordering 问题
*/
@JCStressTest
@Outcome(id = {"0, 1","0, 0","1, 1"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
@Outcome(id = "1, 0", expect = Expect.FORBIDDEN, desc = "INTERESTING")
@State
public static class Case1{
int x;
int y;
static VarHandle Y;
static {
try {
Y = MethodHandles.lookup().findVarHandle(Case1.class, "y", int.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
@Actor
public void actor1(II_Result r) {
x = 1;
Y.setRelease(this, 1);
}
@Actor
public void actor2(II_Result r) {
r.r1 = (int) Y.getAcquire(this);
r.r2 = x;
}
}
/**
* 案例2: 演示 VarHandle 能解决 total ordering 的情况
*/
@JCStressTest
@Outcome(id = {"1, 0","0, 0","0, 1"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
@Outcome(id = "1, 1", expect = Expect.FORBIDDEN, desc = "INTERESTING")
@State
public static class Case2{
int x;
int y;
static VarHandle X;
static VarHandle Y;
static {
try {
X = MethodHandles.lookup().findVarHandle(Case2.class, "x", int.class);
Y = MethodHandles.lookup().findVarHandle(Case2.class, "y", int.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
@Actor
public void actor1(II_Result r) {
r.r2 = (int) X.getAcquire(this);
Y.setRelease(this, 1);
}
@Actor
public void actor2(II_Result r) {
r.r1 = (int) Y.getAcquire(this);
X.setRelease(this, 1);
}
}
/**
* 案例3: 演示 VarHandle 不能解决 total ordering 的情况
*/
@JCStressTest
@Outcome(id = {"1, 0","1, 1","0, 1"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "INTERESTING")
@State
public static class Case3{
int x;
int y;
static VarHandle X;
static VarHandle Y;
static {
try {
X = MethodHandles.lookup().findVarHandle(Case3.class, "x", int.class);
Y = MethodHandles.lookup().findVarHandle(Case3.class, "y", int.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
@Actor
public void actor1(II_Result r) {
Y.setRelease(this, 1);
r.r2 = (int) X.getAcquire(this);
}
@Actor
public void actor2(II_Result r) {
X.setRelease(this, 1);
r.r1 = (int) Y.getAcquire(this);
}
}
}
(1)案例分析: partial ordering
case 1,如果 y=1 先发生,那么前面的 Store 屏障会阻止 x=1 排下去,而后面的 Load 屏障会阻止后续的 r.r2=x排上来
如果 y=1 后发生,那么 Store 屏障会阻止 x=1 排下去,而 Load 屏障会阻止后续的 r.r2=x 排上去,同样,无法控制 r.r2=x 和 x=1 的执行顺序(case 2 case 3)还有可能是 x=1 在 r.r1=y 之前,但结果与 case 3 相同
(2)案例分析:total ordering
case 4,注意看红色的屏障之间的代码仍然可以被重排、蓝色的也一样,因为 case 3 中,红色 Load 屏障只能阻止下面的代码上不来,Store 屏障只能阻止上面的代码下不去,但在此之间,代码顺序无法保证