4

I see strange result when dumping a call stack from a call using Executors and callable. The method from the callable appears twice in the call stack.

pool-1-thread-1@454, prio=5, in group 'main', status: 'RUNNING'
      at com.test.tracked.ACallable.call(ACallable.java:15)
      at com.test.tracked.ACallable.call(ACallable.java:9)
      at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
      at java.util.concurrent.FutureTask.run(FutureTask.java:166)
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
      at java.lang.Thread.run(Thread.java:722)

As you can see, the method ACallable appears twice in the stack:The line 9 is the declaration of the ACallable class and the line 15 is the method signature:

package com.test.tracked;

import java.util.concurrent.Callable;

/**
 *  A callable test class.
 *  
 */
public final class ACallable
    implements Callable<String> {
  private final String who;

  @Override
  public String call() throws Exception {
    Thread.dumpStack();
    return "Hello "+who+" from callable";
  }

  public ACallable(String who) {
    this.who = who;
  }

}

The 'main' thread:

main@1, prio=5, in group 'main', status: 'WAIT'
      at sun.misc.Unsafe.park(Unsafe.java:-1)
      at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
      at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:834)
      at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:994)
      at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1303)
      at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:248)
      at java.util.concurrent.FutureTask.get(FutureTask.java:111)
      at com.test.tracked.AsynchronousCall.callFuture(AsynchronousCall.java:26)
      at com.test.Main.main(Main.java:21)

Code calling the callable:

package com.test.tracked;

import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeoutException;



/**
 * Aynchronous call
 */
public class AsynchronousCall implements Closeable {

  private final ExecutorService executorService;


  public AsynchronousCall() {
    executorService = Executors.newSingleThreadExecutor();
  }

  public String callFuture(String who) throws InterruptedException, ExecutionException, TimeoutException {
    Thread.dumpStack();
    String ret = executorService.submit(new ACallable(who)).get();
    System.out.println("callFuture from " + getClass().getName() + " return " + ret);
    return ret;
  }

  @Override
  public void close() throws IOException {
    executorService.shutdownNow();
  }
}
omartin
  • 135
  • 1
  • 16

2 Answers2

4

The compiler adds a synthetic bridge method to support generics. So

@Override
public String call() throws Exception {
    Thread.dumpStack();
    return "Hello "+who+" from callable";
}

in the compiled .class file is really two methods

// actual method
public Object call() throws Exception {
    return call(); // the other call
} 

// your implementation
public String call() throws Exception {
    Thread.dumpStack();
    return "Hello "+who+" from callable";
}

(Note that this wouldn't be possible in source code because both methods have the same signature.)

This is further explained in other questions and answers:

Community
  • 1
  • 1
Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
3

This is a result of the bridge method added by the compiler.

In the JVM, methods don't have covariant return types. An attempt to call Object call() will not call String call() - if there is no Object call() method, it will throw a NoSuchMethodError. So the compiler adds a bridge method similar to:

public Object call() throws Exception {
    return call(); // the call() method that returns String, not this one
}

Why is there an Object call() method at all? Because of generic type erasure - bytecode does not use generic types. In bytecode, Callable looks like this:

interface Callable {
    Object call() throws Exception;
}

and ACallable looks like this: (this is not valid Java source code, because it contains two methods with the same name and arguments)

class ACallable implements Callable {
    private final String who;

    // does NOT override the method in Callable due to the different return type
    public String call() throws Exception {
        Thread.dumpStack();
        return "Hello "+who+" from callable";
    }

    // overrides the method in Callable
    public Object call() throws Exception {
        return call(); // the version of call() that returns String
    }
}

You can see this by running javap on the generated class file.

user253751
  • 57,427
  • 7
  • 48
  • 90