Calling a lambda expression from a new thread before the main method is run causes the thread to lock up
David Holmes
david.holmes at oracle.com
Tue Jan 24 09:39:15 UTC 2017
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, forax at univ-mlv.fr wrote:
>
>
> ------------------------------------------------------------------------
>
> *De: *"Luke Hutchison" <luke.hutch at gmail.com>
> *À: *"Remi Forax" <forax at univ-mlv.fr>
> *Cc: *"David Holmes" <david.holmes at oracle.com>,
> core-libs-dev at openjdk.java.net
> *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 <forax at univ-mlv.fr
> <mailto:forax at univ-mlv.fr>> 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
>
More information about the core-libs-dev
mailing list