【Java】VarHandle解析

5 篇文章 1 订阅

一、VarHandle简介

变量句柄(VarHandle)是对于一个变量的强类型引用,或者是一组参数化定义的变量族,包括了静态字段、非静态字段、数组元素等,VarHandle支持不同访问模型下对于变量的访问,包括简单的read/write访问,volatile read/write访问,以及CAS访问。

VarHandle相比于传统的对于变量的并发操作具有巨大的优势,在JDK9引入了VarHandle之后,JUC包中对于变量的访问基本上都使用VarHandle,比如AQS中的CLH队列中使用到的变量等。

 

二、VarHandle的作用与优势

在开始讨论VarHandle之前,我们先来回忆一下并发操作里面的三大特性:原子性、可见性、有序性

volatile变量可以保证可见性、有序性(防止指令重拍),加锁或者原子操作、CAS等可以保证原子性,只有同时满足这三个特性才能够保证对于一个变量的并发操作是合乎预期的。

加锁的话需要对线程进行同步,而线程上下文切换之间带来的开销是很大的,所以这里不予考虑,考虑一下几种方式:

  • 使用Atomic包下的原子类进行间接管理,但增加了开销,也可能导致额外的问题如ABA问题;
  • 使用原子性的FieldUpdaters,利用反射机制,开销也会增大;
  • 使用sun.misc.Unsafe提供的JVM内置函数,但直接操作JVM可能会损害安全性和可移植性;

针对以上的问题,VarHandle就是用来替代上述方式的一种方案,它提供了一系列标准的内存屏障操作,用于更细粒度的控制指令排序,在安全性、可用性、性能等方面都要优于现有的AIP,且基本上能够和任何类型的变量相关联。

 

三、创建VarHandle

VarHandle通过MethodHandles类中的内部类Lookup来创建:

public class VarHandleTest {
    private String plainStr;    //普通变量
    private static String staticStr;    //静态变量
    private String reflectStr;    //通过反射生成句柄的变量
    private String[] arrayStr = new String[10];    //数组变量

    private static final VarHandle plainVar;    //普通变量句柄
    private static final VarHandle staticVar;    //静态变量句柄
    private static final VarHandle reflectVar;    //反射字段句柄
    private static final VarHandle arrayVar;    //数组句柄

    static {
        try {
            MethodHandles.Lookup l = MethodHandles.lookup();
            plainVar = l.findVarHandle(VarHandleTest.class, "plainStr", String.class);
            staticVar = l.findStaticVarHandle(VarHandleTest.class, "staticStr", String.class);
            reflectVar = l.unreflectVarHandle(VarHandleTest.class.getDeclaredField("reflectStr"));
            arrayVar = MethodHandles.arrayElementVarHandle(String[].class);
        } catch (ReflectiveOperationException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

}

来分析一下上述创建VarHandle的代码:

1、通过MethodHandles类里面的lookup()函数创建一个Lookup类,这个Lookup类是MethodHandles的内部类,作用就是用于创建方法句柄和变量句柄的一个工厂类,源码如下:

    public static Lookup lookup() {
        return new Lookup(Reflection.getCallerClass());
    }

2、通过Lookup里面的工厂方法生成不同类型的VarHandle,拿生成普通变量的findVarHandle方法来看:

    public VarHandle findVarHandle(Class<?> recv, String name, Class<?> type) 
            throws NoSuchFieldException, IllegalAccessException {
        MemberName getField = resolveOrFail(REF_getField, recv, name, type);
        MemberName putField = resolveOrFail(REF_putField, recv, name, type);
        return getFieldVarHandle(REF_getField, REF_putField, recv, getField, putField);
    }

代码很简单,就是根据传入的参数来生成字段的访问对象MemberName,MemberName是用来描述一个方法或字段引用的数据结构,再根据MemberName生成句柄,熟悉JVM的同学看到REF_getField应该能够联想到JVM字节码,这个就是对应着访问字段的字节码。

对于findStaticVarHandle、unreflectVarHandle方法的实现其实也很类似,大致就是将REF_getField改为REF_getStatic的过程。

 

四、VarHandle的访问

一开始我们就讲过,VarHandle支持不同访问模型下对于变量的访问,包括简单的read/write访问,volatile read/write访问,以及CAS访问等,那么分别来看一下VarHandle是如何支持这些访问模型下对于变量的访问的。

1、简单的read/write访问

    plainVar.set(this, "I am a plain string");    //实例变量的普通write操作
    staticVar.set("I am a static string");    //    静态变量的普通write操作
    reflectVar.set(this, "I am a string created by reflection");    //反射字段的普通write操作
    arrayVar.set(arrayStr, 0, "I am a string array element");    //数组变量的普通write操作

    String plainString = (String) plainVar.get(this);    //实例变量的普通read操作
    String staticString = (String) staticVar.get();    //    静态变量的普通read操作
    String reflectString = (String) staticVar.get(this);    //反射字段的普通read操作
    String arrayStrElem = (String) arrayVar.get(arrayStr, 0);    //数组变量的普通read操作, 第二个参数是指数组下标,即第0个元素

2、volatile read/write访问

对于不同类型的变量的访问方法跟上面其实大同小异,下面就以普通变量来举例:

    volatileVar.setVolatile(this, "I am volatile string");    //volatile write
    String volatileString = (String) volatileVar.get(this);    //volatile read

3、CAS访问

    String casString = (String) casVar.get(this);    //先读取当前值作为cas的预期值
    casVar.compareAndSet(this, casString, "I am a new cas string");    //第二个参数为预期值,第三个参数为修改值

 

五、VarHandle中的指令重排序影响

VarHandle中定义了多种不同的访问模式,定义在VarHandle内部的枚举类里面:

    public enum AccessMode {

        GET("get", AccessType.GET),

        SET("set", AccessType.SET),

        GET_VOLATILE("getVolatile", AccessType.GET),

        SET_VOLATILE("setVolatile", AccessType.SET),

        GET_ACQUIRE("getAcquire", AccessType.GET),

        SET_RELEASE("setRelease", AccessType.SET),

        ...
    }

上面只是VarHandle中定义的访问模式中的一小部分,实际上还有很多,不同的访问模式对于指令重排的效果都可能不一样,因此需要慎重选择访问模式。

其次,在创建VarHandle的过程中,VarHandle内部的访问模式会覆盖变量声明时的任何指令排序效果。比如说一个变量被声明为volatile类型,但是调用varHandle.get()方法时,其访问模式就是get模式,即简单读取方法,因此需要多加注意。

 

参考:https://blog.csdn.net/sench_z/article/details/79793741

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java DBC文件解析是指使用Java编程语言对DBC文件进行解析和处理的过程。DBC文件是CAN网络通信协议的描述文件,它定义了CAN协议的消息,信号,节点等信息。 在Java解析DBC文件涉及到以下几个步骤: 1. 导入相关的Java库:首先需要导入相关的Java库来处理和解析DBC文件。比如,可以使用Apache POI库来读取和解析Excel格式的DBC文件,或者使用自定义的DBC解析库。 2. 读取DBC文件:使用Java代码读取和加载DBC文件,可以将DBC文件读取到内存中,以便后续的解析和处理。 3. 解析DBC文件:解析DBC文件的过程包括解析消息、信号、节点等信息。根据DBC文件的格式和结构,可以使用Java代码逐行解析DBC文件,并将解析的结果存储到相应的数据结构中,比如使用Java的类和对象来表示消息、信号和节点等。 4. 处理和利用解析结果:解析完DBC文件后,可以根据解析结果进行各种操作和处理。例如,可以根据解析的节点信息,生成相应的代码来实现CAN节点的功能;或者根据解析的消息和信号信息,构建CAN消息的发送和接收逻辑。 5. 错误处理和异常处理:在解析DBC文件的过程中,可能会遇到各种错误和异常情况,比如文件路径错误、文件格式错误等。在Java代码中需要适当处理这些错误和异常情况,以保证程序的稳定性和可靠性。 总之,Java DBC文件解析是一项比较复杂的任务,需要熟悉DBC文件的格式和结构,以及相关的Java编程知识和技巧。通过解析和处理DBC文件,可以方便地获取和利用CAN协议的消息和信号等信息,从而实现相应的功能和应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值