javamultithreadingthreadpoolexecutorservicecallable

Does callable also gets executed in a thread?


When we pass a runnabble to an executorService like

Future future = executorService.submit(runnable);

// As here executorService maps the object into instance of

new FutureTask(runnable); and after that it executes the runnable using addWorker(runnable) -> which internally uses thread for execution.

As talking about callable

Future future = executorService.submit(callable); // As here executorService maps the object into instance of

new FutureTask(callable);

How does JVM maps the callable and make it run concurrently. How does it uses the Thread to execute callable and maps the return result of the callable method in FutureTask instance.

because Thread has a very tight contract with run() method, How does JVM manages to call the call() method of callable if it uses a Thread to execute the callable.


Solution

  • Yes, the Callable gets executed by whichever thread grabs the task.

    I think you're giving Runnable too much importance. The Thread class does implement Runnable, but that is not what makes the code multithreaded. That comes from Java starting an OS-level thread when you call the Thread#start() method (ignoring virtual threads). After starting the thread, that new thread then calls the Thread#run() method. Any code that gets executed as a consequence of that run() method being executed is executed on said new thread (ignoring communication between threads).

    By default, the Thread#run() method simply calls the Runnable#run() method on the Runnable instance given to the Thread when it was instantiated.

    Now, you seem to be talking about ThreadPoolExecutor. Note that is only one implementation of ExecutorService, but I will focus on it, too. Internally, ThreadPoolExecutor creates one or more Thread instances and starts them when and as appropriate. These Threads do not execute your submitted Runnable or Callable implementations directly. Instead, each Thread is created with an internal implementation of Runnable: Worker.

    Vastly simplified, this worker essentially does the following:

    while (!shutdown) {
        Runnable task = taskQueue.take();
        task.run();
    }
    

    Where taskQueue is an instance of BlockingQueue<Runnable>. When you submit a Runnable or Callable, they get put in this queue. This is how tasks are submitted by one thread but executed by another.

    However, the Runnable or Callable you submit is not put in the queue directly. First it wraps your object in another that understands how to communicate a result back. In other words, an implementation of Future. But this Future still needs to also be a Runnable, which is where the RunnableFuture interface comes from. The implementation of this interface that is used by ThreadPoolExecutor is FutureTask, at least by default (you can override certain methods to return your own implementation). So, by default, it is a FutureTask that wraps your object that is actually put into the queue.

    The FutureTask class internally wraps any Runnable in a Callable. Again simplified, it essentially does:

    // just the constructors
    
    public FutureTask(Callable<V> callable) {
        this.callable = callable;
    }
    
    public FutureTask(Runnable r, V result) {
        this.callable = () -> {
            r.run();
            return result;
        };
    }
    

    Note: If you do executorService.submit(runnable) then result will ultimately be null.

    Then the FutureTask#run() method (as seen here) executes the call() method of the Callable. Once again vastly simplified, it essentially does:

    @Override
    public void run() {
        try {
            this.result = this.callable.call();
        } catch (Exception ex) {
            this.error = ex;
        }
    }
    

    Note: This ignores the relatively complex code needed to synchronize the state of the task between threads, so that e.g., calls to get() wait for the result as needed (whether a return value or an exception). If you're interested in that, take a look at the implementation linked above.

    Given the FutureTask#run() method is executed by whatever thread grabbed it from the task queue, the Callable will be executed by said thread.