Does callable also gets executed in a thread?

huangapple go评论82阅读模式
英文:

Does callable also gets executed in a thread?

问题

当我们将一个Runnable传递给ExecutorService时:

Future future = executorService.submit(runnable);

在这里,executorService将该对象映射到FutureTask<T>的实例中,然后执行runnable,使用addWorker(runnable),它在内部使用线程来执行。

关于Callable

Future future = executorService.submit(callable);

JVM如何映射Callable并使其并发运行?它如何使用线程来执行Callable,并将Callable方法的返回结果映射到FutureTask实例中?

因为线程与run()方法有着紧密的约定,所以JVM如何管理调用Callablecall()方法,如果它使用线程来执行Callable呢?

英文:

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<T>(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<T>(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.

答案1

得分: 4

是的,Callable 会被抓取任务的任何线程执行。

我认为你对 Runnable 给予了过多的重要性。Thread 类确实实现了 Runnable,但这并不是使代码多线程化的关键因素。这是因为在调用 Thread#start() 方法时,Java会启动一个操作系统级别的线程(忽略虚拟线程),然后新线程会调用 Thread#run() 方法。任何由于执行 run() 方法而执行的代码都将在新线程上执行(忽略线程之间的通信)。

默认情况下,Thread#run() 方法只是在 Thread 实例被实例化时给定的 Runnable 实例上调用 Runnable#run() 方法。

现在,你似乎在谈论 ThreadPoolExecutor。请注意,这只是 ExecutorService 的一种实现,但我也将重点关注它。在内部,ThreadPoolExecutor 创建一个或多个 Thread 实例,并在适当的时候启动它们。这些 Thread 并不直接执行你提交的 RunnableCallable 实现。相反,每个 Thread 都使用 Runnable 的内部实现创建,称为 Worker

大致简化来说,这个工作线程本质上执行以下操作:

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

其中 taskQueueBlockingQueue&lt;Runnable&gt; 的一个实例。当你提交一个 RunnableCallable 时,它们会被放入这个队列中。这就是一个线程提交任务,但由另一个线程执行的方式。

但是,你提交的 RunnableCallable 并不是直接放入队列中的。首先,它将你的对象包装在另一个对象中,该对象知道如何返回结果。换句话说,它是 Future 的一种实现。但这个 Future 仍然需要是一个 Runnable,这就是 RunnableFuture 接口的来源。ThreadPoolExecutor 使用的是 FutureTask,至少默认情况下(你可以重写某些方法以返回自己的实现)。因此,默认情况下,实际放入队列的是包装了你的对象的 FutureTask

FutureTask 类在内部将任何 Runnable 包装在 Callable 中。再次简化,它实际上执行以下操作:

// 仅构造函数

public FutureTask(Callable&lt;V&gt; callable) {
    this.callable = callable;
}

public FutureTask(Runnable r, V result) {
    this.callable = () -&gt; {
        r.run();
        return result;
    };
}

**注意:**如果你执行 executorService.submit(runnable),那么 result 最终将为 null

然后,FutureTask#run() 方法(如此处所示)执行 Callablecall() 方法。再次大致简化,它实际上执行以下操作:

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

**注意:**这忽略了在线程之间同步任务状态所需的相对复杂的代码,以便等待需要等待结果(无论是返回值还是异常)的 get() 调用。如果你对此感兴趣,可以查看上面链接的实现。

由于 FutureTask#run() 方法是由从任务队列中获取它的线程执行的,因此 Callable 将由该线程执行。

英文:

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&lt;Runnable&gt;. 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&lt;V&gt; callable) {
    this.callable = callable;
}

public FutureTask(Runnable r, V result) {
    this.callable = () -&gt; {
        r.run();
        return result;
    };
}

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

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;
    }
}

<sup>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.</sup>

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

huangapple
  • 本文由 发表于 2023年1月6日 13:45:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/75027360.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定