英文:
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如何管理调用Callable
的call()
方法,如果它使用线程来执行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
并不直接执行你提交的 Runnable
或 Callable
实现。相反,每个 Thread
都使用 Runnable
的内部实现创建,称为 Worker
。
大致简化来说,这个工作线程本质上执行以下操作:
while (!shutdown) {
Runnable task = taskQueue.take();
task.run();
}
其中 taskQueue
是 BlockingQueue<Runnable>
的一个实例。当你提交一个 Runnable
或 Callable
时,它们会被放入这个队列中。这就是一个线程提交任务,但由另一个线程执行的方式。
但是,你提交的 Runnable
或 Callable
并不是直接放入队列中的。首先,它将你的对象包装在另一个对象中,该对象知道如何返回结果。换句话说,它是 Future
的一种实现。但这个 Future
仍然需要是一个 Runnable
,这就是 RunnableFuture
接口的来源。ThreadPoolExecutor
使用的是 FutureTask
,至少默认情况下(你可以重写某些方法以返回自己的实现)。因此,默认情况下,实际放入队列的是包装了你的对象的 FutureTask
。
FutureTask
类在内部将任何 Runnable
包装在 Callable
中。再次简化,它实际上执行以下操作:
// 仅构造函数
public FutureTask(Callable<V> callable) {
this.callable = callable;
}
public FutureTask(Runnable r, V result) {
this.callable = () -> {
r.run();
return result;
};
}
**注意:**如果你执行 executorService.submit(runnable)
,那么 result
最终将为 null
。
然后,FutureTask#run()
方法(如此处所示)执行 Callable
的 call()
方法。再次大致简化,它实际上执行以下操作:
@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 Thread
s 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;
};
}
<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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论