java - 创建的 Runnables 的 lambda 的不同行为

标签 java multithreading lambda java-8 runnable

下面是两组通过 Java 8 lambda 创建多个可运行对象的代码。但是,我发现对象创建的行为有所不同。

代码 1 - 创建多个对象,因为哈希码不同

PasswordUtils passwordUtils = new PasswordUtils();
for(int i = 0 ; i < 100 ; i++) {
    Runnable r = () -> passwordUtils.print();            //difference       
    System.out.println(r.hashCode());
}

输出 -

1129670968
1023714065
.. //varies in each iteration

代码 2 - 创建单个对象,因为所有对象的哈希码都相同。

for(int i = 0 ; i < 100 ; i++) {
    Runnable r = () -> new PasswordUtils().print();      //difference       
    System.out.println(r.hashCode());
}

输出-

1254526270
1254526270
... // same on all iterations

区别在于创建PasswordUtils对象的时间。造成这种差异的原因是什么?

编辑:只需完成代码即可使用

class PasswordUtils {
    void print() {
    }
}

最佳答案

此代码重现了您的发现:

public class Main {
    public static void main(String[] args) throws IOException {
        Object passwordUtils = new Object();
        for(int i = 0 ; i < 10 ; i++) {
            Runnable r = () -> passwordUtils.toString();
            System.out.println(r.hashCode());
        }

        System.out.println("-------");

        for(int i = 0 ; i < 10; i++) {
            Runnable r = () -> new Object().toString();
            System.out.println(r.hashCode());
        }
    }
}

编译为:

METHOD: main([Ljava/lang/String;)V
--------------------------------------
L0:
{
    NEW
    DUP
    INVOKESPECIAL   java/lang/Object/<init>()V
    ASTORE_1

    //int I = 0
    ICONST_0
    ISTORE_2
}

L2:
{
    ILOAD_2
    BIPUSH  10
    IF_ICMPGE  L3
}

L4:
{
    ALOAD_1
    INVOKEDYNAMIC   java/lang/invoke/LambdaMetafactory/metafactory (MethodHandles$Lookup, String, MethodType, MethodType, MethodHandle, MethodType)CallSite; : run(Object)Runnable;
    ASTORE_3
}

L5:
{
    GETSTATIC   java/lang/System/out Ljava/io/PrintStream;
    ALOAD_3
    INVOKEVIRTUAL   java/lang/Object/hashCode()I
    INVOKEVIRTUAL   java/io/PrintStream/println(I)V
}

L6:
{
    IINC
    GOTO  L2
}

L3:
{
    GETSTATIC   java/lang/System/out Ljava/io/PrintStream;
    LDC  "-------"
    INVOKEVIRTUAL   java/io/PrintStream/println(Ljava/lang/String;)V
}

L7:
{
    ICONST_0
    ISTORE_2
}

L8:
{
    ILOAD_2
    BIPUSH  10
    IF_ICMPGE  L9
}

L10:
{
    INVOKEDYNAMIC   java/lang/invoke/LambdaMetafactory/metafactory (MethodHandles$Lookup, String, MethodType, MethodType, MethodHandle, MethodType)CallSite; : run()Runnable;
    ASTORE_3
}

L11:
{
    GETSTATIC   java/lang/System/out Ljava/io/PrintStream;
    ALOAD_3
    INVOKEVIRTUAL   java/lang/Object/hashCode()I
    INVOKEVIRTUAL   java/io/PrintStream/println(I)V
}

L12:
{
    IINC
    GOTO  L8
}

L9:
{
    RETURN
}



METHOD: lambda$main$1()V
--------------------------------------
L0:
{
    NEW
    DUP
    INVOKESPECIAL   java/lang/Object/<init>()V
    INVOKEVIRTUAL   java/lang/Object/toString()Ljava/lang/String;
    POP
    RETURN
}

METHOD: lambda$main$0(Ljava/lang/Object;)V
--------------------------------------

L0:
{
    ALOAD_0
    INVOKEVIRTUAL   java/lang/Object/toString()Ljava/lang/String;
    POP
    RETURN
}

请注意,第一个 InvokeDynamic 指令具有 ALOAD_1,而另一个则没有。ALOAD_1 是捕获的对象 passwordUtils 变量..

上面的 InvokeDynamic 指令转换为:

//Calls Runnable.run with a captured variable `passwordUtils` -> #1
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        CallSite site = LambdaMetafactory.metafactory(
                lookup,
                "run",
                MethodType.methodType(Runnable.class, Object.class),
                MethodType.methodType(void.class),
                lookup.findStatic(Main.class, "Main$lambda$1", MethodType.methodType(void.class, Object.class)),
                MethodType.methodType(void.class)
        );

        MethodHandle factory = site.getTarget();
        Runnable r = (Runnable)factory.invoke(new Object());
        //r.run();
        System.out.println(r.hashCode());

//Calls Runnable.run() -> #2
MethodHandles.Lookup lookup = MethodHandles.lookup();
        CallSite site = LambdaMetafactory.metafactory(
                lookup,
                "run",
                MethodType.methodType(Runnable.class),
                MethodType.methodType(void.class),
                lookup.findStatic(Main.class, "Main$lambda$2", MethodType.methodType(void.class)), //doesn't capture
                MethodType.methodType(void.class)
        );

        MethodHandle factory = site.getTarget();
        Runnable r = (Runnable)factory.invoke();
        //r.run();
        System.out.println(r.hashCode());

如果您将 #1 和 #2 封装在 for 循环中并运行它们,则每次都会重新创建 CallSite 对象,因此会产生不同的哈希码。

但是,如果您像 JVM 一样对其进行优化,您会注意到一个调用点被缓存,而另一个则没有。

IE:

for (int i = 0; i < 10; ++i)
{
    Runnable r = (Runnable)factory.invoke();
    System.out.println(r.hashCode());
}

在捕获变量的情况下,每次都会返回一个新的Runnable(如果无法优化调用点,它可以修改方法句柄),并且在函数体保持不变(即:不捕获),每次都会返回相同的 Runnable(因为它在内部调用 InvokeExact)。

/**
     * Invokes the method handle, allowing any caller type descriptor,
     * and optionally performing conversions on arguments and return values.
     * <p>
     * If the call site's symbolic type descriptor exactly matches this method handle's {@link #type type},
     * the call proceeds as if by {@link #invokeExact invokeExact}.
     * <p>
     * Otherwise, the call proceeds as if this method handle were first
     * adjusted by calling {@link #asType asType} to adjust this method handle
     * to the required type, and then the call proceeds as if by
     * {@link #invokeExact invokeExact} on the adjusted method handle.
     * <p>
     * There is no guarantee that the {@code asType} call is actually made.
     * If the JVM can predict the results of making the call, it may perform
     * adaptations directly on the caller's arguments,
     * and call the target method handle according to its own exact type.
     * <p>
     * The resolved type descriptor at the call site of {@code invoke} must
     * be a valid argument to the receivers {@code asType} method.
     * In particular, the caller must specify the same argument arity
     * as the callee's type,
     * if the callee is not a {@linkplain #asVarargsCollector variable arity collector}.
     * <p>
     * When this method is observed via the Core Reflection API,
     * it will appear as a single native method, taking an object array and returning an object.
     * If this native method is invoked directly via
     * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI,
     * or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect},
     * it will throw an {@code UnsupportedOperationException}.
     * @param args the signature-polymorphic parameter list, statically represented using varargs
     * @return the signature-polymorphic result, statically represented using {@code Object}
     * @throws WrongMethodTypeException if the target's type cannot be adjusted to the caller's symbolic type descriptor
     * @throws ClassCastException if the target's type can be adjusted to the caller, but a reference cast fails
     * @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call
     */
    public final native @PolymorphicSignature Object invoke(Object... args) throws Throwable;

关于java - 创建的 Runnables 的 lambda 的不同行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62716090/

相关文章:

c - 在多线程应用程序中使用基于 openssl 的 pbkdf2

c++ - 与 std::semi_regular 一起使用时,带有捕获的 lambda 无法编译

node.js - 如何解析AWS lambda上的body参数?

java - 在 Java Web 应用程序中实现 WebLogic Workmanager

java - ORA-01861 : literal doesn't match format string

Java:为什么两个类中的两个文字(相同的内容)使用相同的内部字符串?

c++ - 当主 GUI 线程被阻塞时,如何从工作线程创建无模式对话框?

java - 在运行时更改 Android Camera 2 的 Flash 设置

java - 创建多线程循环

java - 从 lambda 抛出异常