I have to second Remi's view here - hidden concurrency is an accident waiting to happen, far too many things can go wrong if the users of your API don't know that new threads can be involved.

It's not wrong, per-se, to start threads from a static initializer - just wrong to do something that has to wait, in the static initializer, for that other thread to do something, which in turn depends on being able to access the class being initialized.

In this case, as Remi explained (thanks!), the desugared lambda-code becomes a static method on the enclosing class, and executing a static method requires that the class is initialized.

David

On 24/01/2017 6:39 PM, [email protected] wrote:


------------------------------------------------------------------------

    *De: *"Luke Hutchison" <[email protected]>
    *À: *"Remi Forax" <[email protected]>
    *Cc: *"David Holmes" <[email protected]>,
    [email protected]
    *Envoyé: *Mardi 24 Janvier 2017 09:13:17
    *Objet: *Re: Calling a lambda expression from a new thread before
    the main method is run causes the thread to lock up

    On Tue, Jan 24, 2017 at 12:02 AM, Remi Forax <[email protected]
    <mailto:[email protected]>> wrote:

        a worker thread of the executor will try to execute the code of
        the static method but because the static initializer is not
        finished, the worker thread has to wait


    But what is the worker thread waiting for in the case of the lambda
    that it is not waiting for in the case of the AIC? I assume this is
    due to the lexical scoping of the lambda?


it's not directly related to the lexical scoping of a lambda, it's
related to the fact that the body of a lambda is not inside the class
created when you transform a lambda to a class that implements the
functional interface but the body of a lambda is part of the class that
declares the lambda.


        As a rule of thumb, never starts a thread in a static block, you
        will get this classical deadlock AND your code is hard to test
        because static blocks tend to  be executed in a random order
        (i.e. class loading is lazy so execution of static blocks are
        lazy too).


    This is the problem: I am a library author, and a user of my library
    ran into this problem. The user did not necessarily know that I was
    launching new threads in my library, and as a library author, I did
    not know I needed to advise users that they should not call my
    library from static blocks. It seems to be quite a big problem that
    a static block is not a standard execution context and can lead to
    deadlocks.


Users should always know when they starts a thread, otherwise they may
send you objects that are not thread safe, multi-threading is not
something you can hide to your users.
Concurrency is already hard, hidden concurrency is harder :)

static blocks are executed with a kind of class lock to prevent several
threads to initialize the same class, so like any synchronized-like
block, they should be as small as possible.


    Is there any way I can detect this in my code? I guess I could just
    get a stacktrace, and look for "<init>" or similar somewhere in the
    stacktrace, then throw an exception if called in this context? Is
    this a reliable enough test for being run in a static initializer block?


The real name of a static block is <clinit>, not <init> which is used
for constructor.
If your API clearly states that you will start a thread, the burden to
fix deadlocks is transferred to your users :)
The extreme of that is that some people use the javadoc to cover their
own ass, "yes, i know this behavior is stupid but look, it's written in
the javadoc".


Here is your code slightly modified, with no lambda, that deadlock too:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NoLambdaBug {
  static {
    startUp();
  }

  static void iamstatic() {
    System.out.println("Inner class method executed");
  }

  private static void startUp() {
    System.out.println("Entering startUp()");
    ExecutorService es = Executors.newSingleThreadExecutor();
    try {
      Callable<Void> callable = new Callable<Void>() {
        @Override
        public Void call() throws Exception {
          iamstatic();
          return null;
        }
      };
      es.submit(callable).get();
    } catch (InterruptedException | ExecutionException e) {
      throw new RuntimeException(e);
    } finally {
      es.shutdown();
    }
    System.out.println("Exiting startUp()");
  }

  public static void main(String[] args) {
    System.out.println("Exiting main");
  }
}

Rémi

Reply via email to