2

PS: Sorry for my poor english.I can't describe the problem clearly. :(

When I don't use the "lambda method reference" code style in the static block, like:

static{
map.keySet().parallelStream().forEach(e -> {
            System.out.println(e);
        });
}

then the program running forever, never stop.

But when I change the code to

static{
map.keySet().parallelStream().forEach(System.out::println);
}

then the bug gone.The program can finish immediately.

Just look directly at the code please, I already tried to simplify the code as far as I can.

public class BugSimulate {

    static {
        init();
    }

    private static void init() {
        Map<Integer, String> map = new HashMap<>();

        int i = 0;
        map.put(++i, "1");
        map.put(++i, "1");
        map.put(++i, "1");
        map.put(++i, "1");
        map.put(++i, "1");
        map.put(++i, "1");
        map.put(++i, "1");
        map.put(++i, "1");
        map.put(++i, "1");

        // running forever
        map.keySet().parallelStream().forEach(e -> {
            System.out.println(e);
        });

        // finish immediately
        //        map.keySet().parallelStream().forEach(System.out::println);
    }

    @Test
    public void test() {
        new BugSimulate();
    }
}

enter image description here

but when I change the lambda code

e -> {System.out.println(e);}

to

System.out::println

the program finish immediately

enter image description here

Or I change parallelStream() to normal stream(),Bugs gone.

Or I remove the static blocks,Bugs gone too.

My jdk version is 1.8.0_202

And the OS version is MacOS 10.14.5

ACBingo
  • 1,078
  • 1
  • 8
  • 13

1 Answers1

5

Interesting Question: This question is similar with Class Loading with Static Block Competition Cause Deadlock,

but for this issue, it's caused by when use e -> {System.out.println(e);} after compiling, it will generate an synthetic method: anonymous static private method for this, as:

  // access flags 0x100A
  private static synthetic lambda$init$0(Ljava/lang/Integer;)V
   L0
    LINENUMBER 27 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 0
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L1
    LINENUMBER 28 L1
    RETURN
   L2
    LOCALVARIABLE e Ljava/lang/Integer; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1

this synthetic method: private static method is generated by e -> {System.out.println(e);} compile.

also it's similar equal to:

static {
    init();
}

private static void init() {
    Map<Integer, String> map = new HashMap<>();

    int i = 0;
    map.put(++i, "1");
    ***
    Thread foo = new Thread() {
        @Override
        public void run() {
            print(map.keySet().iterator().next()); //try access the private method, this cause a competition with **Class Initializer** and **static block**
        }
    };

    foo.start();
    try {
        foo.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
//equal to e -> {System.out.println(e);}
private static void print(int t) {
    System.out.println(t);
}

so as Class Loading with Static Block Competition Cause Deadlock this post explanation: when init class BugSimulate, it will invoke static block firstly, but since it's using parallelStream and try to invoke the generated private anonymous method, this caused an competition with class initializer, so finally it's deadlock.

Competition

chengpohi
  • 14,064
  • 1
  • 24
  • 42
  • 1
    That's interesting. I have run the "bug" code and it worked without deadlock. And why this variant works correctly `System.out::println`? – v.ladynev Jun 19 '19 at 08:54
  • 2
    @v.ladynev when use `System.out::println` will invoke this static method directly: `GETSTATIC java/lang/System.out : Ljava/io/PrintStream;` – chengpohi Jun 19 '19 at 09:18
  • Thanks a lot,Now I understand.The **anonymous method in lambda** will generate at runtime,But **normal anonymous method** generated at compile time.Thats the very important point which I don't know and then caused my confusion – ACBingo Jun 19 '19 at 09:51
  • 2
    @ACBingo but it’s worth mentioning that you should not rely on subtle implementation details, e.g. while `Stream.of("a", "b", "c", "d").parallel().forEach(System.out::println);` does not deadlock, because it creates a direct delegate, `Stream.of("a", "b", "c", "d").parallel().forEach(System.out::printf);` may deadlock, as it will invoke a synthetic method of your class like a lambda expression, to prepare the array for the varargs invocation. The takeaway of the linked Q&As is that you should avoid multi-threaded operations in class initializers in general. – Holger Jun 19 '19 at 09:54
  • @Holger,Thanks a lot!So the 'printf’ method will invoke a synthetic method, which means it also will generate some anonymous class (or method) at running time too? In the end,will caused an competition with class initializer – ACBingo Jun 19 '19 at 10:04
  • 3
    @ACBingo the synthetic method is generated at compile-time. The class implementing the functional interface will be generated at runtime. There will always be a runtime generated class, but for certain method references, it will invoke the target method directly. In contrast, for lambda expressions and certain nontrivial method references (varargs, intersection types, special access modifiers), it will invoke a compiler generated method in your class, which can cause these deadlocks. – Holger Jun 19 '19 at 10:48