Search code examples
javaexceptionmonitorcompletable-future

Simple CompletableFuture.supplyAsync() leads to IllegalMonitorStateException error


I'm trying this in java8:

  public static void main(String[] args) throws Exception {
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(
      () -> { return 911; });
    future.whenComplete(
      (result, error) -> {
        System.out.println("Alarm:" + result);
        error.printStackTrace();
      }
    );
    future.wait();
  }

Upon running, I got this output:

Alarm:911
[WARNING]
java.lang.IllegalMonitorStateException
    at java.lang.Object.wait (Native Method)
    at java.lang.Object.wait (Object.java:502)
    at mygroup.UseCompletableFuture.main (UseCompletableFuture.java:15)
    at org.codehaus.mojo.exec.ExecJavaMojo$1.run (ExecJavaMojo.java:254)
    at java.lang.Thread.run (Thread.java:748)

Is the "error" information expected? Did I miss anything in my code snippet?


Solution

  • The exception whose stack trace is shown is thrown by future.wait() and is not related to the error argument of the second CompleableFuture. It occurs because wait() requires the thread invoking it to be holding the object's monitor. See this for a description of what it means for a thread to be owner of the object's monitor. Your code basically has to be within a synchronized block in order to be able to call wait() or notify(). You probably intended to call get(), the wait()/notify() methods are generic synchronization methods inherited by Object and have been there since the beginning of Java.

    As for the error parameter, it is indeed null, because the first stage completes normally. Hence, the line error.printStackTrace() should thrown a NPE. However, your code simply does not handle. It is thrown in the async thread that executes the stage, and remains silent. See this post in relation to this behavior.

    I suspect you want to call the get() method on the future. First you should assign the second stage to a variable (future is still referencing the first stage):

    future = future.whenComplete(
          (result, error) -> {
            System.out.println("Alarm:" + result);
            System.out.println(error);
            error.printStackTrace();
          }
    );
    

    Then you could call get() on it, which allows you to handle an ExecutionException which will wrap the NPE occurring in the second stage:

    try {
        result = future.get();
        System.out.println(result);
    } catch (InterruptedException e) {
        ...
    } catch (ExecutionException e) {
        ...
    }