Java 21: First Release Candidate
openjdk.org
openjdk.org
I'm building a course for people who are new to Java concurrency and I've made the assumption that from September, you will almost always _choose_ virtual threads over platform threads. The default virtual thread management surely matches what most people want most of the time (1 worker thread per CPU core, virtual threads on top). It removes a lot of the old worries about management of thread pools and how OS thread implementation details "leak" into Java.
It's brought it much closer to how I'd written coaching material for multithreading in Go, but the Executor abstractions will still let Java programmers shift over from platform threads at their own pace.
Locks become more relevant. Old java you might have limited concurrency using thread pool size. With virtual threads something like a blocking queue or semaphore is likely a better choice.
Reactive java should be avoided (IMO). It was a decent choice for old java, for virtual thread java you get little benefits from it.
This will probably change, but with old java `synchronized` was alright. Virtual thread java you want to avoid that.
This likely won't change, ThreadLocalStorage is a bad idea with virtual threads, you almost certainly want to use ScopedValues instead.
You should understand the implications of structured concurrency. It's not fully ready yet, but at very least the `try(var service = newVirtualThreadPoolExecutor)` does work (I believe) which will block until all tasks launched are completed. This is likely desirable but might be unexpected if you wanted to fire and forget something.
Otherwise, I think all the old concurrency advice applies.
This is interesting, we used RxJava quite a bit on Android until Kotlin Coroutines made things a bit easier in that the code read more linearly and worked nicely with built in language features like exceptions for error handling.
Just curious if you agree with this in Java, or had other reasons.
It colors your entire code base, prevents natural error handling, and quite frankly makes maintenance that much more complex.
Obviously there’s a place for it, but every project I’ve been on that used it shouldn’t have.
Do virtual threads make reactive within that model unnecessary too? Seems maybe not? Reactive is both a model, and a paradigm, no?
Even with virtual threads, there will still be a reason to write reactive programs it seems.
I work on moderate sized async reactive pipeline with lots of CompletableFutures throughout (and chaining of them so you can process a batch in parallel and commit contiguous offsets periodically) and there's several bugs that have been undebuggable because they happpen _infrequently enough_ in prod, the heap dump is huge, and there's no stack trace to tell you what is going on. The best you can do is lots lots and lots of logging and turn it into an analytics problem to find out what happened (to be fair, logging is only marginally better than wading through a heap dump of completable futures).
I've made my fair share of completable future messes with a lot of biconsume/thenApply/handle nonsense to try and avoid blocking.
The only difference is virtual threads have platform support which means even better stack traces and no need to decorate your method.
So yup, agree.
`ReentrantLock` pretty much replaces every use case of `synchronize` and also supports `fairness`. The only downside of `ReentrantLock` is making the code more verbose, and inexperienced programmers might forget to release the lock.
From the mailing list it sounds like the plan is to fix this, I hope that happens sooner rather than later.
Go was originally non-preemptive ("the loops problem"). This was changed in 2019 to preempt at safepoints [3]. The Java team will probably have to do this too [4], but similarly it is low on their priority list and requires feedback.
java.util.concurrent is still the basis of good Java concurrency. Some external libraries for reactive or actors might become less popular, but they were always the minority. There will be some improvements with structured concurrency to make that a little bit easier, but the fundamental approach will remain the same. I think old advice will be relevant, except when it comes to thread pool sizing (which was always a black art). At best some over eager use of async will go away and we'll return to simpler blocking code.
[1] https://github.com/openjdk/loom/commit/24e80bdee3eaf347f2eab...
[2] https://mail.openjdk.org/pipermail/loom-dev/2023-July/005990...
[3] https://go.dev/src/runtime/preempt.go
---
Here's an excerpt from the book "Java Threads" by Scott Oaks and Henry Wong, published by O'Reilly Media, which discusses green threads:
"Green threads provide a thread abstraction to the application, allowing the programmer to write multithreaded programs without worrying about the underlying OS support. However, because they're implemented at the user level, they are not subject to preemptive multitasking by the underlying OS. Instead, they must voluntarily yield control to other threads. If a green thread fails to do this, it can effectively freeze all other green threads in the system."
This excerpt supports the fact that green threads were not preemptive and relied on voluntary cooperation to switch control between threads.
Depending upon your POV this may be a positive or negative. You can get around it by manually inserting some sleeps() - note that yield() won't work (which is kinda unfortunate).
E.g. I'm building an app that does a lot of file IO and I won't be using virtual threads. They would provide nothing for this particular use case.
do you know any docs/examples how this can be integrated with current java io libraries?.
That being said my biggest hurdles are always combining blocking with async code. That usually bloats my code. Examples are blocking LDAP search that needs not block the event loop.
0 - https://vertx.io/docs/apidocs/io/vertx/core/Future.html
1 - https://vertx.io/docs/apidocs/io/vertx/core/Promise.html
The most abstract I'm usually willing to get is using River [5] instead of array's map/filter/reduce.
There seems to be an absolute obsession over these libraries now though. I assume it's fueled by every frontend library pushing functional patterns. See Effect [1] for example, it's insane how much is piled on.
[2] type Fun<A, B> = (a: A) => B export const pipe = <A, B>(f: Fun<A, B>) => { return { to: <C>(g: Fun<B, C>) => pipe((arg: A) => g(f(arg))), start: () => f, }; };
[4] class TaskQueue<U = any> { private pending: Promise<any> = Promise.resolve(); add = <T,>(task: () => Promise<false extends true & U ? T : U>) => (this.pending = this.pending.then(task).catch(task)); }
Java 21: What’s New? - https://news.ycombinator.com/item?id=37067491 - Aug 2023 (255 comments)
Java users can finally have some decent readable concurrent I/O without the blight of reactive.
This is a huge win.
(https://a.co/d/1XGdjWQ is an Amazon link to Solaris Internals)
In general the overhead of a thread is around 1kb compared to the 1mb system thread overhead. That's fantastic.
I think green threads are a much better was to solve the problem as its much easier to reason about and debug.
* Nullsafe typing.
* Nullsafe typing!
* Inline extension functions for existing APIs. These are supremely useful. You can write features like locking and unlocking around a block, or try-with-resources in a one-liner, without compromising performance.
* (Nested) When statements, if-else as expression etc.
* Reified generics. You can actually write: "if (xy is T)" or use T::class
* Nullsafe typing
* Data classes
* Getter/Setter syntax as property and indexing syntax.
* Only unchecked exceptions
Kotlin really is Java 2.0.
But on a serious note it’s far too complex of a decision that includes your personal situation. I have a mixed codebase of Java and Kotlin and I tend to prefer Java now. I do use Kotlin’s Data classes a lot though. That’s my favorite feauture of the language.
All in all you could use a Kotlin data class just like you'd use a Java record, but it gives you much more flexibility like inheritance and mutable fields.
And string templates looked exciting to me. But after skimming https://openjdk.org/jeps/430
it sounds like they want to avoid backticks for "safety"? Like we can already use the plus operator to append strings why would it be any less safe to give us an ergonomic way to do that? Oh well, Java isn't exactly known for being ergonomic.
Using STR."" instead of STR.`` is just an aesthetic choice, and one that I think makes sense for Java.
Overloading that behavior by default would likely blow up a lot of code and certainly violate the principle of least surprise.
No one else has such an oddity
And I fail to see how it's much of an oddity, as other RDBs use all kinds of BS for literals. SQL Server for example uses [square brackets].
If anything backticks have some history of being used for string literals in some languages (e.g. Javascript and Go), whereas in many others they are used for command substitution (which is not far removed from what a string literal combined with a processor does in Java).
Different for the purpose of being different.
In reality, this will take a bunch of time to actually show much real results. We've been producing async future driven libraries for a long time, and they're generally pretty good at keeping the computer busy while sending out many concurrent IO requests. The real benefits are when these libraries start to support these virtual threads which should ideally lead to less complexity and some lesser compute overhead (my hopes).
For server tasks, it may simply be reducing the total number of OS threads necessary to do the same tasks, since we won't need to depend on farming out work to work pools, so it may actually make some simpler uses of coroutines unnecessary, if I can simply receive request, process, ask blocking source for info, then respond in one virtual thread, why would I need the extra complexity/cost of coroutines for this naive use case anymore?
I imagine a lot of the missing structured concurrency will get built up over time on the Java side, but it is an interesting talk regardless.
As a developer if you're targeting the JVM with virtual threads you may decide to use those for some workloads instead of coroutines.
In the future when targeting the JVM Kotlin maybe can use the features exposed by Project Loom to execute coroutines more efficiently, but it's not clear to me that there's much for Kotlin to gain there, maybe some small gains in performance.
And since they also want to compete with Swift and TypeScript, on their own turf, even less likely.
This is the thing with guest languages, cannot embrace the platform, always full of compromises.
record A(B b) {}If you want a data-only object, use records.
* Maven (or at the very least transitive dependency management) is a defacto standard. While some solutions exist such as Poetry, there is no such as thing as a centralized repository with POMs that tell you all the related dependencies easily. And managing virtual environments feels like a huge step backward.
* I ended up using FastAPI + Dependency Injector for the Python project, and while great, it is less robust, less well documented than things like Spring + JAX-RS
* Because Python sucks at multithreading and is slow, everyone has a bunch of C in their libraries, so when there is a bug (which there will always be), it's very hard to debug. In Java land I can easily step through any code, including the JVM.
* Lack of typing metadata - even relatively new libraries like the OpenAI client lack typing information, leaving you guessing about what data is available to work with.
* SQL Alchemy is vastly inferior to Java ORM solutions IMO (no, I don't want to discuss ORMs...)
* IDE support is still relatively primitive for pytest. Until a couple weeks ago, you couldn't see the output of all your tests in the VS Code console log until it was done executing. Or when you get an error, you can't just click on the stack trace to go to the failing line.
* Lack of autogenerated API documentation, e.g. equivalent of Javadoc
Love the Python syntax, but I still feel like I was more productive in Java for writing webapps. YMMV
It's fascinating just observing at a higher level our different systems and what makes it through as bugs after all testing, code review etc is done.
Java - null pointers are the main thing.
Python - it's a dogs breakfast. Latest bug was someone forgetting a comma in a list of strings since ['foo' 'bar'] is valid. But all kinds of crazy stuff - kwargs everywhere with people putting invalid values or types in. Straight up incorrect numbers of args going into functions which our linters don't notice for some reason. Refactoring anything widely used is a highly risky endeavour. Even with type hinting across all the exposed interfaces, Python is like a sieve for bugs, they just fall straight through. Once you get a complicated enough system, you just have to rely on total test coverage (ideally integration / end to end) or just letting your users find the bugs.
Also what all others have mentioned below.
It's mature and absurdly stable, both the language and the ecosystem; the tooling is best in class, and a lot of the awkward chafing points are going away to the point where the language is pretty pleasant to work with...
It isn't an exciting language. It's boring, it's the last to get new cool features, and that's by design. It also never breaks. There's extremely rarely any library churn or surprises. You can spend all the time working instead of putting out fires caused by the ecosystem.