MutableCallSite 是一个 CallSite ,其目标变量的行为类似于普通字段。链接到 MutableCallSite 的 invokedynamic 指令将所有调用委托给站点的当前目标。可变调用站点的 动态调用程序 还将每次调用委托给站点的当前目标。
这是一个可变调用站点的示例,它将状态变量引入到方法句柄链中。
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(MethodHandle target) 使用初始目标方法句柄创建调用站点对象。MutableCallSite(MethodType type) 使用给定的方法类型创建一个空白调用站点对象。 -
方法总结
修饰符和类型方法描述final MethodHandle生成等效于已链接到此调用站点的 invokedynamic 指令的方法句柄。final MethodHandle返回调用站点的目标方法,其行为类似于MutableCallSite的普通字段。voidsetTarget(MethodHandle newTarget) 将此调用站点的目标方法更新为普通变量。static voidsyncAll(MutableCallSite[] sites) 在给定数组中的每个调用站点上执行同步操作,强制所有其他线程丢弃以前从任何调用站点的目标加载的任何缓存值。
-
构造方法详细信息
-
MutableCallSite
使用给定的方法类型创建一个空白调用站点对象。初始目标设置为给定类型的方法句柄,如果调用它将抛出IllegalStateException。呼叫站点的类型永久设置为给定类型。
在从引导程序方法返回此
CallSite对象或以其他方式调用之前,通常会通过调用setTarget为其提供更有用的目标方法。- 参数:
type- 此调用站点将具有的方法类型- 抛出:
NullPointerException- 如果建议的类型为 null
-
MutableCallSite
使用初始目标方法句柄创建调用站点对象。调用站点的类型永久设置为初始目标的类型。- 参数:
target- 将成为调用站点初始目标的方法句柄- 抛出:
NullPointerException- 如果提议的目标为空
-
-
方法详情
-
getTarget
返回调用站点的目标方法,其行为类似于MutableCallSite的普通字段。getTarget与内存的交互与从普通变量(例如数组元素或非易失性非最终字段)读取相同。特别是,当前线程可能会选择重用先前从内存中读取目标的结果,并且可能看不到另一个线程最近对目标的更新。
-
setTarget
将此调用站点的目标方法更新为普通变量。新目标的类型必须与旧目标的类型一致。与内存的交互与写入普通变量(例如数组元素或非易失性非最终字段)相同。
特别是,不相关的线程可能无法看到更新的目标,直到它们从内存中执行读取。通过将适当的操作放入引导程序方法和/或在任何给定调用站点使用的目标方法,可以创建更强的保证。
- 指定者:
setTarget在类CallSite中- 参数:
newTarget- 新目标- 抛出:
NullPointerException- 如果提议的新目标为空WrongMethodTypeException- 如果提议的新目标的方法类型与之前的目标不同- 参见:
-
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
在给定数组中的每个调用站点上执行同步操作,强制所有其他线程丢弃以前从任何调用站点的目标加载的任何缓存值。此操作不会撤消已经在旧目标值上启动的任何调用。 (Java 仅支持 向前的时间旅行。)
总体效果是迫使每个调用站点目标的所有未来读者接受最近存储的值。 (“最近”是相对于
syncAll本身计算的。)相反,syncAll调用可能会阻塞,直到所有读者(以某种方式)解除缓存每个调用站点目标的所有先前版本。为避免竞争条件,对
setTarget和syncAll的调用通常应在某种互斥条件下执行。请注意,读取器线程可能会在安装该值的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数组引用为空或数组包含空
- 一个新的 volatile 变量
-