模块 java.base

类 MutableCallSite

java.lang.Object
java.lang.invoke.CallSite
java.lang.invoke.MutableCallSite
已知子类:
AbstractRelinkableCallSite

public non-sealed class MutableCallSite extends CallSite
MutableCallSite 是一个 CallSite ,其目标变量的行为类似于普通字段。链接到 MutableCallSiteinvokedynamic 指令将所有调用委托给站点的当前目标。可变调用站点的 动态调用程序 还将每次调用委托给站点的当前目标。

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


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());
  

Non-synchronization of target values: 写入可变调用站点的目标不会强制其他线程了解更新后的值。未针对更新的调用站点执行适当同步操作的线程可能会缓存旧目标值并无限期地延迟它们对新目标值的使用。 (这是将 Java 内存模型应用于对象字段的正常结果。)

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

对于经常更新的目标值,请考虑改用 不稳定的呼叫站点

自从:
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 与内存的交互与从普通变量(例如数组元素或非易失性非最终字段)读取相同。

      特别是,当前线程可能会选择重用先前从内存中读取目标的结果,并且可能看不到另一个线程最近对目标的更新。

      指定者:
      getTarget 在类 CallSite
      返回:
      此调用站点的链接状态,一个可以随时间变化的方法句柄
      参见:
    • setTarget

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

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

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

      指定者:
      setTarget 在类 CallSite
      参数:
      newTarget - 新目标
      抛出:
      NullPointerException - 如果提议的新目标为空
      WrongMethodTypeException - 如果提议的新目标的方法类型与之前的目标不同
      参见:
    • dynamicInvoker

      public final MethodHandle  dynamicInvoker()
      生成等效于已链接到此调用站点的 invokedynamic 指令的方法句柄。

      此方法等效于以下代码:

      
       MethodHandle getTarget, invoker, result;
       getTarget = MethodHandles.publicLookup().bind(this, "getTarget", MethodType.methodType(MethodHandle.class));
       invoker = MethodHandles.exactInvoker(this.type());
       result = MethodHandles.foldArguments(invoker, getTarget)
        
      指定者:
      dynamicInvoker 在类 CallSite
      返回:
      始终调用此调用站点的当前目标的方法句柄
    • syncAll

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

      此操作不会撤消已经在旧目标值上启动的任何调用。 (Java 仅支持 向前的时间旅行。)

      总体效果是迫使每个调用站点目标的所有未来读者接受最近存储的值。 (“最近”是相对于 syncAll 本身计算的。)相反,syncAll 调用可能会阻塞,直到所有读者(以某种方式)解除缓存每个调用站点目标的所有先前版本。

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

      此操作可能很昂贵,应谨慎使用。如果可能的话,应该缓冲它以便在调用站点集上进行批处理。

      如果 sites 包含空元素,将引发 NullPointerException。在这种情况下,方法异常返回之前可能会处理数组中的某些非空元素。这些是哪些元素(如果有的话)是依赖于实现的。

      Java 内存模型详细信息

      就 Java 内存模型而言,此操作执行同步操作,其效果相当于当前线程写入 volatile 变量,并由可能访问受影响的调用站点之一的每个其他线程最终读取 volatile。

      对于每个单独的调用站点 S,以下效果是显而易见的:

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

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

      Discussion: 出于性能原因,syncAll 不是单个调用站点上的虚拟方法,而是适用于一组调用站点。某些实现可能会因处理一个或多个同步操作而产生大量固定开销成本,但每个额外的调用点都会产生少量增量成本。在任何情况下,此操作都可能代价高昂,因为可能必须以某种方式中断其他线程才能使它们注意到更新的目标值。然而,可以观察到同步多个站点的单个调用与许多调用具有相同的正式效果,每个调用仅在一个站点上。

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

      参数:
      sites - 要同步的调用站点数组
      抛出:
      NullPointerException - 如果 sites 数组引用为空或数组包含空