Глядя в код Акки, я думаю, что нашел то, что он делает. Я не совсем уверен, но почти: akka ActorSystem создает Dispatchers
, который создает MessageDispatcherConfigurator
, который создает Dispatcher
, который создает ExecutorService (я передаю иерархию классов этого). Существует несколько возможных реализаций, но я думаю, что это наиболее распространенная ситуация, и именно это происходит при использовании ForkJoinPool.
Теперь Dispatcher расширяет BatchingExecutor
, который может объединять внутреннюю задачу, такую как карта в вопросе (для запуска которой требуется поток), в текущий поток.
Еще раз, код слишком сложен для меня, чтобы быть уверенным, и я не буду исследовать больше. Но действительно, akka EC может обернуть внутренний вызов карты в родительский поток, чего не происходит со стандартным (то есть java) ForkJoinPool.
Я думаю, что это умный трюк от akka, а не типичная реализация.
Документ BatchingExecutor говорит:
/**
* Mixin trait for an Executor
* which groups multiple nested `Runnable.run()` calls
* into a single Runnable passed to the original
* Executor. This can be a useful optimization
* because it bypasses the original context's task
* queue and keeps related (nested) code on a single
* thread which may improve CPU affinity. However,
* if tasks passed to the Executor are blocking
* or expensive, this optimization can prevent work-stealing
* and make performance worse. Also, some ExecutionContext
* may be fast enough natively that this optimization just
* adds overhead.
* The default ExecutionContext.global is already batching
* or fast enough not to benefit from it; while
* `fromExecutor` and `fromExecutorService` do NOT add
* this optimization since they don't know whether the underlying
* executor will benefit from it.
* A batching executor can create deadlocks if code does
* not use `scala.concurrent.blocking` when it should,
* because tasks created within other tasks will block
* on the outer task completing.
* This executor may run tasks in any order, including LIFO order.
* There are no ordering guarantees.
*
* WARNING: The underlying Executor's execute-method must not execute the submitted Runnable
* in the calling thread synchronously. It must enqueue/handoff the Runnable.
*/