It's not about feature / promise return types. Consider:
int someFunc() {
doA();
doB();
byte data[] = readFromSocket();
return doC(data);
}
int callerFunc() {
doX();
System.out.println(someFunc());
doY();
}
when we invoke the callerFunc() in a virtual thread, it executes till the potentially blocking socket reading, creates a callback or future -like object containing doC(), System.out.println(), doY() - such object is called a "continuation", see "continuation passing style". Then the socket reading is iniitated in an async way, with continuation registered as a callback to be invoked upon completion. Then the native OS thread is freed to do any other work.
In javascript approach we would need to manually mark some places `async` and use `await`.
@async
int someFunc() {
doA();
doB();
byte[] data = await readFromSocket();
return doC(data);
}
@async
int callerFunc() {
doX();
System.out.println(await someFunc());
doY();
}
So async / await is a poor man's continuation passing style.
The need to differentiate between 'async' and non 'async' code makes it more difficult to refactor or mix code between 'async' and non 'async' domains.
Some people argue that it's better to have the explicit distinction. I personally don't see benefits of this.
Your examples are not functionally equivalent. In the first example the callers of both functions are blocked, in the second the callers are not blocked.
That's the point of Java's virtual threads, basically nothing* will block. The JVM can simply replace a blocking user IO call to an async one under the hood, and in the meanwhile schedule another virtual thread to work. When the IO is ready the suspended thread might get continued.
In your example the thread is not blocked but the callers are blocked.
There is more to async/await than simply keeping os threads unblocked, they also provide a mechanism for keeping callers unblocked and synchronizing async contexts(parallel or concurrent).
Is Loom addressing this need? Otherwise it's Goroutines(or any green threading solution) without channels and select. The ecosystem will fracture around solutions to address the boiler and future chaining pains.
Java allows not blocking callers using Futures or callbacks for a long time.
As well as Promises and callbacks were available in Javascript before async / await.
var executor = Executors.newVirtualThreadPerTaskExecutor();
;; or, for old Java
var executor = Executors.newSingleThreadExecutor();
Future<Integer> f = executor.submit(someFunc);
What is relevant in the new Java VirtualThreads and Javascript async / await
is the possibility to write simple synchronous code, with the performance
similar to callback-based asynchronous code.
But that arguably doesn’t use this feature to its fullest — the most naive/easy to comprehend way to do concurrency is to start up multiple threads calculating something and simply wait for all of them to return, and at this point you are free to use the calculated results as is. This is the most-idiomatic way to make java virtual threads (with a try-with-resources block)