模块  java.base
软件包  java.lang.invoke

Class MutableCallSite

  • 已知直接子类:
    AbstractRelinkableCallSite

    public class MutableCallSite
    extends CallSite
    A MutableCallSiteCallSite其目标变量的行为类似于普通字段。 链接到MutableCallSiteinvokedynamic指令委托对站点当前目标的所有调用。 可变调用站点的dynamic invoker还会将每次调用委派给站点的当前目标。

    下面是一个可变调用站点的示例,它将状态变量引入方法句柄链。

    
    MutableCallSite name = new MutableCallSite(MethodType.methodType(String.class));
    MethodHandle MH_name = name.dynamicInvoker();
    MethodType MT_str1 = MethodType.methodType(String.class);
    MethodHandle MH_upcase = MethodHandles.lookup()
        .findVirtual(String.class, "toUpperCase", MT_str1);
    MethodHandle worker1 = MethodHandles.filterReturnValue(MH_name, MH_upcase);
    name.setTarget(MethodHandles.constant(String.class, "Rocky"));
    assertEquals("ROCKY", (String) worker1.invokeExact());
    name.setTarget(MethodHandles.constant(String.class, "Fred"));
    assertEquals("FRED", (String) worker1.invokeExact());
    // (mutation can be continued indefinitely)
     

    同一个呼叫站点可以同时在几个地方使用。

    
    MethodType MT_str2 = MethodType.methodType(String.class, String.class);
    MethodHandle MH_cat = lookup().findVirtual(String.class,
      "concat", methodType(String.class, String.class));
    MethodHandle MH_dear = MethodHandles.insertArguments(MH_cat, 1, ", dear?");
    MethodHandle worker2 = MethodHandles.filterReturnValue(MH_name, MH_dear);
    assertEquals("Fred, dear?", (String) worker2.invokeExact());
    name.setTarget(MethodHandles.constant(String.class, "Wilma"));
    assertEquals("WILMA", (String) worker1.invokeExact());
    assertEquals("Wilma, dear?", (String) worker2.invokeExact());
     

    目标值不同步:写入可变调用站点的目标不会强制其他线程知道更新的值。 不相对于更新的呼叫站点执行适当的同步动作的线程可以缓存旧的目标值并且无限地延迟它们对新目标值的使用。 (这是应用于对象字段的Java内存模型的正常结果。)

    syncAll操作提供了一种强制线程接受新目标值的方法,即使没有其他同步也是如此。

    对于将频繁更新的目标值,请考虑使用volatile call site

    从以下版本开始:
    1.7
    • 构造方法详细信息

      • MutableCallSite

        public MutableCallSite​(MethodType type)
        使用给定的方法类型创建一个空白的调用站点对象。 初始目标设置为给定类型的方法句柄,如果调用它将抛出IllegalStateException

        呼叫站点的类型永久设置为给定类型。

        在从引导方法返回CallSite对象之前,或以其他方式调用之前,通常通过调用setTarget它提供更有用的目标方法。

        参数
        type - 此调用站点将具有的方法类型
        异常
        NullPointerException - 如果建议的类型为null
      • MutableCallSite

        public MutableCallSite​(MethodHandle target)
        使用初始目标方法句柄创建调用站点对象。 呼叫站点的类型永久设置为初始目标的类型。
        参数
        target - 将成为调用站点的初始目标的方法句柄
        异常
        NullPointerException - 如果建议的目标为空
    • 方法详细信息

      • getTarget

        public final MethodHandle getTarget()
        返回调用站点的目标方法,其行为类似于MutableCallSite的普通字段。

        getTarget与内存的交互与普通变量的读取相同,例如数组元素或非易失性非最终字段。

        特别地,当前线程可以选择从存储器重用先前读取的目标的结果,并且可能无法看到另一个线程对目标的最近更新。

        Specified by:
        getTarget ,类 CallSite
        结果
        此调用站点的链接状态,一个可以随时间变化的方法句柄
        另请参见:
        setTarget(java.lang.invoke.MethodHandle)
      • setTarget

        public void setTarget​(MethodHandle newTarget)
        更新此调用站点的目标方法,作为普通变量。 新目标的类型必须与旧目标的类型一致。

        与内存的交互与对普通变量的写入相同,例如数组元素或非易失性非最终字段。

        特别是,不相关的线程在执行内存读取之前可能无法看到更新的目标。 通过将适当的操作放入引导方法和/或任何给定呼叫站点使用的目标方法,可以创建更强的保证。

        Specified by:
        setTarget在类 CallSite
        参数
        newTarget - 新目标
        异常
        NullPointerException - 如果建议的新目标为空
        WrongMethodTypeException - 如果建议的新目标的方法类型与先前的目标不同
        另请参见:
        getTarget()
      • syncAll

        public static void syncAll​(MutableCallSite[] sites)
        对给定数组中的每个调用站点执行同步操作,强制所有其他线程丢弃先前从任何调用站点的目标加载的任何缓存值。

        此操作不会撤消已在旧目标值上启动的任何调用。 (Java仅支持forward time travel.

        总体效果是强制每个呼叫站点的目标的所有未来读者接受最近存储的值。 (“最近”是相对于syncAll本身计算的。)相反, syncAll呼叫可能会阻止,直到所有读者都(以某种方式)分解了每个呼叫站点目标的所有先前版本。

        为避免竞争条件,通常应在某种互斥条件下执行对setTargetsyncAll调用。 请注意,读取器线程可能会在安装该值的setTarget调用之前(以及确认该值的syncAll之前)观察到更新的目标。 另一方面,读取器线程可能会观察到目标的先前版本,直到syncAll调用返回(以及尝试传达更新版本的setTarget之后)。

        这种操作可能很昂贵,应该谨慎使用。 如果可能,应对其进行缓冲,以便在多组呼叫站点上进行批处理。

        如果sites包含null元素,则将引发NullPointerException 在这种情况下,可以在方法异常返回之前处理数组中的一些非null元素。 这些元素(如果有的话)取决于实现。

        Java内存模型详细信息

        就Java内存模型而言,此操作执行同步操作,该操作与当前线程写入易失性变量的效果相当,并且可以访问可能访问受影响的呼叫站点之一的每个其他线程的最终易失性读取。

        对于每个呼叫站点S ,以下效果是显而易见的:

        • 创建一个新的volatile变量V ,并由当前线程写入。 根据JMM的定义,此写入是全局同步事件。
        • 正如使用写入事件的线程本地排序一样,当前线程已执行的每个操作都在易失性写入V之前发生。 (在某些实现中,这意味着当前线程执行全局释放操作。)
        • 具体而言,对当前目标S的写入将在易失性写入V之前发生。
        • 对全局同步顺序(以特定于实现的方式)放置对V的易失性写入。
        • 考虑一个任意线程T (当前线程除外)。 如果T在易失性写入V (在全局同步顺序中)之后执行同步操作A ,则因此如果它在目标上执行读取,则需要查看当前目标S或稍后写入该目标。 S (此约束称为“同步顺序一致性”。)
        • JMM特别允许优化编译器忽略已知无用的变量的读取或写入。 这种省略的读写对前发关系没有影响。 无论这个事实如何,易失性V都不会被省略,即使它的写入值是不确定的并且不使用它的读取值。
        因为最后一点,实施行为就像一个挥发性读V被执行T其行动后立即A T的本地操作顺序中,此读取在将来读取目标S之前发生。 就好像实现任意选择S的目标读取T ,并强制读取V在它之前,从而确保新目标值的通信。

        只要遵守Java内存模型的约束,实现可能会延迟syncAll操作的完成,而其他线程(上面的T )继续使用先前值S的目标。 但是,(一如既往)鼓励实现避免活锁,并最终要求所有线程都考虑更新的目标。

        讨论:出于性能原因, syncAll不是单个呼叫站点上的虚拟方法,而是适用于一组呼叫站点。 一些实现可能导致用于处理一个或多个同步操作的大的固定开销成本,但是每个附加呼叫站点的增量成本很小。 在任何情况下,该操作可能是昂贵的,因为可能必须以某种方式中断其他线程以使它们注意到更新的目标值。 但是,可以观察到,同步多个站点的单个呼叫具有与许多呼叫相同的正式效果,每个呼叫仅在一个站点上。

        实现注意: MutableCallSite简单实现可以使用volatile变量作为可变调用站点的目标。 在这样的实现中, syncAll方法可以是无操作,但它将符合上面记录的JMM行为。

        参数
        sites - 要同步的一组呼叫站点
        异常
        NullPointerException - 如果 sites数组引用为null或数组包含null