- 直接已知的子类:
-
AbstractRelinkableCallSite
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()); // (变异可以无限继续)
同一个调用站点可以同时在多个地方使用。
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 Summary
ConstructorDescriptionMutableCallSite
(MethodHandle target) 创建一个具有初始目标方法句柄的调用站点对象。MutableCallSite
(MethodType type) 创建一个具有给定方法类型的空白调用站点对象。 -
Method Summary
Modifier and TypeMethodDescriptionfinal MethodHandle
生成等效于已链接到此调用站点的invokedynamic指令的方法句柄。final MethodHandle
返回调用站点的目标方法,其行为类似于MutableCallSite
的普通字段。void
setTarget
(MethodHandle newTarget) 更新此调用站点的目标方法,作为普通变量。static void
syncAll
(MutableCallSite[] sites) 对给定数组中的每个调用站点执行同步操作,强制所有其他线程放弃先前从任何调用站点的目标加载的缓存值。
-
Constructor Details
-
MutableCallSite
创建一个具有给定方法类型的空白调用站点对象。初始目标设置为给定类型的方法句柄,如果调用则会抛出一个IllegalStateException
。调用站点的类型永久设置为给定类型。
在从引导方法返回此
CallSite
对象之前,或以其他方式调用之前,通常通过调用setTarget
为其提供一个更有用的目标方法。- 参数:
-
type
- 此调用站点将具有的方法类型 - 抛出:
-
NullPointerException
- 如果提议的类型为null
-
MutableCallSite
创建一个具有初始目标方法句柄的调用站点对象。调用站点的类型永久设置为初始目标的类型。- 参数:
-
target
- 将是调用站点初始目标的方法句柄 - 抛出:
-
NullPointerException
- 如果提议的目标为null
-
-
Method Details
-
getTarget
返回调用站点的目标方法,其行为类似于MutableCallSite
的普通字段。getTarget
与内存的交互与从普通变量(如数组元素或非易失性、非最终字段)读取的交互相同。特别是,当前线程可能选择重用先前从内存中读取的目标的结果,并可能无法看到另一个线程对目标的最近更新。
-
setTarget
更新此调用站点的目标方法,作为普通变量。新目标的类型必须与旧目标的类型一致。与内存的交互与写入普通变量(如数组元素或非易失性、非最终字段)的交互相同。
特别是,不相关的线程可能直到从内存中执行读取操作之前才能看到更新后的目标。通过在引导方法和/或任何给定调用站点使用的目标方法中放入适当的操作,可以创建更强的保证。
- 指定者:
-
setTarget
在类CallSite
中 - 参数:
-
newTarget
- 新目标 - 抛出:
-
NullPointerException
- 如果提议的新目标为null -
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内存模型而言,此操作执行的同步操作类似于当前线程写入易失性变量,以及可能访问受影响调用点之一的每个其他线程的最终易失性读取。对于每个单独的调用点
S
,以下效果是明显的:- 创建一个新的易失性变量
V
,并由当前线程写入。根据JMM的定义,此写入是全局同步事件。 - 与线程本地写入事件排序一样,当前线程已执行的每个操作都被视为发生在对
V
的易失性写入之前。(在某些实现中,这意味着当前线程执行全局释放操作。) - 具体来说,对
S
的当前目标的写入被视为发生在对V
的易失性写入之前。 - 对
V
的易失性写入以(以实现特定的方式)放置在全局同步顺序中。 - 考虑任意线程
T
(不是当前线程)。如果T
在对V
的易失性写入之后执行同步操作A
(在全局同步顺序中),则它因此需要看到S
的当前目标,或者如果它在S
的目标上执行读取,则需要看到该目标的后续写入。(此约束称为“同步顺序一致性”。) - JMM明确允许优化编译器省略已知无用的变量的读取或写入。这种省略的读取和写入对happens-before关系没有影响。尽管存在这一事实,易失性
V
不会被省略,即使其写入值是不确定的且其读取值未被使用。
T
立即在其操作A
之后执行了对V
的易失性读取一样。在T
的操作的本地排序中,此读取发生在对S
的目标的任何未来读取之前。就好像实现任意选择了T
对S
的目标的读取,并强制要求对V
的读取在其之前发生,从而确保传达新的目标值。syncAll
操作,而其他线程(如上述的T
)继续使用S
的目标的先前值。但是,实现(像往常一样)鼓励避免活锁,并最终要求所有线程考虑更新的目标。讨论:出于性能原因,
syncAll
不是单个调用点上的虚拟方法,而是应用于一组调用点。一些实现可能会为处理一个或多个同步操作产生较大的固定开销,但对于每个额外的调用点则产生较小的增量成本。无论如何,此操作可能是昂贵的,因为必须以某种方式中断其他线程,以使它们注意到更新的目标值。但可以观察到,一次同步多个站点与多次调用,每次仅在一个站点上调用,具有相同的形式效果。实现说明:简单的
MutableCallSite
实现可能会使用易失性变量作为可变调用点的目标。在这种实现中,syncAll
方法可能是一个空操作,但它将符合上述JMM行为。- 参数:
-
sites
- 要同步的调用点数组 - 抛出:
-
NullPointerException
- 如果sites
数组引用为null或数组包含null
- 创建一个新的易失性变量
-