Module java.base
Package java.lang.invoke

Class 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());
// (变异可以无限继续)
 

同一个调用站点可以同时在多个地方使用。


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调用站点

自Java版本:
1.7
  • Constructor Details

    • MutableCallSite

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

      调用站点的类型永久设置为给定类型。

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

      参数:
      type - 此调用站点将具有的方法类型
      抛出:
      NullPointerException - 如果提议的类型为null
    • MutableCallSite

      public MutableCallSite(MethodHandle target)
      创建一个具有初始目标方法句柄的调用站点对象。调用站点的类型永久设置为初始目标的类型。
      参数:
      target - 将是调用站点初始目标的方法句柄
      抛出:
      NullPointerException - 如果提议的目标为null
  • Method Details

    • getTarget

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

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

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

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

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

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

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

      指定者:
      setTarget 在类 CallSite
      参数:
      newTarget - 新目标
      抛出:
      NullPointerException - 如果提议的新目标为null
      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内存模型而言,此操作执行的同步操作类似于当前线程写入易失性变量,以及可能访问受影响调用点之一的每个其他线程的最终易失性读取。

      对于每个单独的调用点S,以下效果是明显的:

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

      讨论:出于性能原因,syncAll不是单个调用点上的虚拟方法,而是应用于一组调用点。一些实现可能会为处理一个或多个同步操作产生较大的固定开销,但对于每个额外的调用点则产生较小的增量成本。无论如何,此操作可能是昂贵的,因为必须以某种方式中断其他线程,以使它们注意到更新的目标值。但可以观察到,一次同步多个站点与多次调用,每次仅在一个站点上调用,具有相同的形式效果。

      实现说明:简单的MutableCallSite实现可能会使用易失性变量作为可变调用点的目标。在这种实现中,syncAll方法可能是一个空操作,但它将符合上述JMM行为。

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