Java 9 变量句柄-VarHandle

  如果要原子性地增加某个字段的值,到目前为止我们可以使用下面三种方式:

  • 使用AtomicInteger来达到这种效果,这种间接管理方式增加了空间开销,还会导致额外的并发问题;
  • 使用原子性的FieldUpdaters,利用反射机制,操作开销也会更大;
  • 使用sun.misc.Unsafe提供的JVM内置函数API,虽然这种方式比较快,但它会损害安全性和可移植性。

  VarHandle 的出现替代了 java.util.concurrent.atomic 和 sun.misc.Unsafe 的部分操作。并且提供了一系列标准的内存屏障操作,用于更加细粒度的控制内存排序。在安全性、可用性、性能上都要优于现有的API。VarHandle 可以与任何字段、数组元素或静态变量关联,支持在不同访问模型下对这些类型变量的访问,包括简单的 read/write 访问,volatile 类型的 read/write 访问,和 CAS(compare-and-swap)等。

  Lookup在Java 9中对变量访问也添加了相应的工厂方法,用于生成变量句柄-VarHandle:

  • findVarHandle:用于创建对象中非静态字段的VarHandle。接收参数有三个,第一个为接收者的class对象,第二个是字段名称,第三个是字段类型。
  • findStaticVarHandle:用于创建对象中静态字段的VarHandle。接收参数与findVarHandle一致。
  • unreflectVarHandle:通过反射字段Field创建VarHandle

  创建VarHandle

  VarHandle通过MethodHandles的相关函数方法创建,下面演示了非静态变量和数组的获取方式:

public class VarhandleFoo {
    volatile int x;
    private Point[] points;

    private static final VarHandle QA;//for arrays
    private static final VarHandle X;//for Variables
    static {
        try {
            QA =  MethodHandles.arrayElementVarHandle(Point[].class);
            X = MethodHandles.lookup().
                    findVarHandle(Point.class, "x", int.class); //or
            //X = MethodHandles.lookup().in(Point.class).findVarHandle(Point.class, "x", int.class);
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }
    }

    class Point {
        // ...
    }
}

  获取VarHandle后,接下来就是对变量的访问,下面列举了几种简单的访问形式:

//plain read
int x = (int) X.get(this);
Point p = (Point) QA.get(points,10);

//plain write
X.set(this,1);
QA.set(points,10,new Point());

//CAS
X.compareAndSet(this,0,1);
QA.compareAndSet(points,10,p,new Point());

//Numeric Atomic Update
X.getAndAdd(this,10);

  VarHandle中的每个方法都被称为**“access mode method”**,方法所接收的参数都是一个协调表达式,首个参数用来指示被访问变量的对象,后续参数表示当前访问模式的操作所需要的值。例如,CAS方法需要两个后续参数:预期值和新值。

  内存排序影响

  访问模式控制着原子性和一致性属性,对于大多数方法来说,有以下几种内存排序效果:

  • 对于引用类型和32位以内的原始类型,read和write(get、set)都可以保证原子性,并且对于执行线程以外的线程不施加可观察的排序约束。
  • 不透明属性:访问相同变量时,不透明操作按原子顺序排列。
  • Acquire模式的读取总是在与它对应的Release模式的写入之后。
  • 所有Volatile变量的操作相互之间都是有序的。

  需要特别注意的是,access mode将覆盖在变量声明时指定的任何内存排序效果。 例如,一个VarHandle使用 get 模式访问一个字段时,即使这个字段已经被声明为volatile,也会把这个字段当做方法指定的访问模式进行访问。因此使用时要非常小心。

public class VarHandleTest {
    volatile int x;
    private Point[] points = new Point[20];

    private static final VarHandle QA;
    private static final VarHandle X;

    static {
        try {
            QA = arrayElementVarHandle(Point[].class);
            X = lookup().findVarHandle(VarHandleTest.class, "x", int.class);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        VarHandleTest varHandleTest = new VarHandleTest();
        X.set(varHandleTest, 1);
        QA.set(varHandleTest.getPoints(), 1, new Point(10));

        int x = (int) X.get(varHandleTest);
        Point p = (Point) QA.get(varHandleTest.getPoints(), 1);
        System.out.println(x + "------------" + p);

        X.compareAndSet(varHandleTest, 1, 10);
        QA.compareAndSet(varHandleTest.getPoints(), 1, p, new Point(2));
        int x1 = (int) X.get(varHandleTest);
        Point p1 = (Point) QA.get(varHandleTest.getPoints(), 1);
        System.out.println(x1 + "------------" + p1);
    }

    static class Point {
        int x;

        public Point() {

        }

        public Point(int x) {
            this.x = x;
        }

        @Override
        public String toString() {
            return "Point{" +
                    "x=" + x +
                    '}';
        }
    }

    public Point[] getPoints() {
        return points;
    }
}

 

posted on 2020-11-22 12:55  溪水静幽  阅读(523)  评论(0编辑  收藏  举报