Scheduling is not enough. For efficient IO you need a scheduler that’s tightly coupled with OS-specific kernel mode APIs for async IO.
Java has support for that in java.nio.channels but that’s limited and is not integrated with the rest of their standard library.
C++ can do that, too, but quite hard in practice.
Runtimes like Go, .NET and erlang already have that stuff included. There’re some limitations (e.g. erlang doesn’t support that on Windows, BTW they call the feature “kernel poll”), but still it works great and very easy to use.
There’re indeed multiple third-party libraries for that in many languages. E.g. for C++ we have lubuv, boost.asio, etc. However, built-in solutions have upsides.
They work out of the box i.e. deployment is simpler.
They’re integrated into the language, e.g. most network IO in golang is asynchronous under the hood, compiler and runtime do that automatically.
They’re integrated into standard libraries, e.g. in .NET all streams be it files, sockets, or transforms like compressors and encryptors support asynchronous operations.