In the .Net world there's the ThreadPool class to manage the single process-wide thread-pool, and the Task class to enqueue and orchestrate concurrent jobs (it uses ThreadPool and almost completely abstracts away the thread management).
(You could write your own thread-pool, of course, but for most code that wouldn't make sense.)
As I understand it, the JVM is rather behind in this department. (Not to mention async/await.)
Java had thread pools and tasks since 2004 (https://docs.oracle.com/javase/1.5.0/docs/api/java/util/conc...). For typical Java programs, no one is writing their own thread pool functionality as far as I know.
Linux containers leak information about the "true" environment in a way that upset JVM assumptions before 9 and 10.
Java possibly makes it a bit easier to work around, really, in that Java forces you to initialize your thread pool, so it wouldn't feel quite so weird to add a "# of threads in thread pool" setting to an app config file for something. I'm guessing that's not the Docker way of doing it, though.
Java just lacks async/await primitives.
Setting up each one becomes a lot of work fast, so we wrote a LD_PRELOAD[1] hook that overrides sysconf[1] _NC_PROCESSORS* call to get number of processors availabale/online to a certain specified value and is baked in the docker image by default during builds.
[1] http://man7.org/linux/man-pages/man3/sysconf.3.html