0
点赞
收藏
分享

微信扫一扫

VarHandle:Java9中保证变量读写可见性、有序性、原子性利器



文章目录

  • 一、什么是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常用方法

VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_java

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排上来

VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_java_02


如果 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 相同

VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_共享变量_03


VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_开发语言_04

(2)案例分析:total ordering

VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_System_05


VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_共享变量_06


VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_System_07


VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_共享变量_08


case 4,注意看红色的屏障之间的代码仍然可以被重排、蓝色的也一样,因为 case 3 中,红色 Load 屏障只能阻止下面的代码上不来,Store 屏障只能阻止上面的代码下不去,但在此之间,代码顺序无法保证

VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_python_09


举报

相关推荐

0 条评论