Hacker News new | past | comments | ask | show | jobs | submit login
Java 21: First Release Candidate (openjdk.org)
146 points by mkurz 9 months ago | hide | past | favorite | 107 comments



How much old advice on Java concurrency will virtual threads change?

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.


Wrangling executor services (practically) becomes an antipattern. Where old java you'd do a singleton threadpool, virtual thread java you'd pretty much never do that. In cases where you might be tempted to do that, the common fork join pool is likely the better choice.

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.


> 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 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.


Even before the introduction of virtual threads RxJava should have been avoided.

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.


I'm curious about this from the perspective of a functional Kotlin backend (using Arrow or not).

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 think it's (looesely) tasks vs threads. Threads have a stack trace and a well defined state in the JVM, tasks are objects on the heap.

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).


There's going to be a difference in the way you do things. Most of the completable future API is going to be unnecessary, .join will work better as there's not penalty for blocking.

I've made my fair share of completable future messes with a lot of biconsume/thenApply/handle nonsense to try and avoid blocking.


The reason to prefer kotlin coroutines is exactly the reason to ditch reactive for virtual threads.

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.


It will be years when most Android users are on Java 21. It’s also not a feature that can be transpiled. So for Android developers virtual threads are probably not a thing we need to understand or use in any near future.


Why stop using 'synchronized'?


`synchronized` is a lock without any ordering/fairness guarantee. Therefore, with the increased concurrency caused by Virtual Threads, there's higher chances for starvation.

`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.


More than that (as referenced above), `synchronized` currently pins a virtual thread to its carrier. So if you had 4 carrier threads, you only need to have 4 synchronized methods in flight that depend on the action of a 5th to hang the JVM.


Got it, so for now using `synchronized` actually prevents the concurrency benefits of virtual thread because it pins the carrier thread. Plus, there's also the possibility of deadlock when mixing `synchronized` and `j.c.u.Lock`.


Pretty much. You won't die if you run over some synchronized code (like ConcurrentHashMap) but you do have to think about what's happening when you hold that lock.

From the mailing list it sounds like the plan is to fix this, I hope that happens sooner rather than later.


I don't think VTs should be used in practice until Java 22, when it is likely that synchronization (monitors) will be supported [1]. A lot of code may synchronize and be non-trivial to convert to a ReentrantLock, such as ConcurrentHashMap's computeIfAbsent. This can lead to surprising failures outside of the application author's control, such as [2].

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

[4] https://github.com/openjdk/loom/tree/preempt-support

[5] https://openjdk.org/jeps/453


Were original Java green threads preemptive (and at safepoints)?


It is sad that Googling didn't find the answer by ChatGPT did.

---

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.


Thanks! I remember that book, but i do not have it.


Only for IO bound tasks, if you are doing a compute bound task that needs to saturate all cores, I am not sure what benefits virtual threads would bring.


The trade off is going to remain the same for parallel algorithms, once saturated performance will gradually decline as swapping threads harms performance. Go for example exposes the CPU Core/thread count because unbounded performs worse even despite its apparently cheap virtual threads. I have tested this a number of times in Java and Go and the situation is similar you want to be using near the CPU count dependent on the algorithm and IO. It will be the same in Java with virtual thread, the cost of the threads will be lower but the optimal number likely wont change much.


In Java, unless they changed something in the past ~6 months, virtual threads will not "swap out" a CPU bound task, you have to have some blocking, otherwise it will keep on chugging.

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).


Does sleep(0) also work?


At the moment (as far as I know), only for NETWORKING bound tasks. Not general IO. Java at the moment doesn't support asynchronous file IO, this makes some sense since async-io on Linux is a bit of a mess still in terms of stability and security (but it's getting there so fingers crossed...).

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.


Depends a lot on what you are doing and if you need preemption. There's not a huge penalty for using a virtual thread for compute (Other than impacting IO bound tasks).


> Only for IO bound tasks

do you know any docs/examples how this can be integrated with current java io libraries?.


So I subscribe to the belief that a Java multi-thread application should have at least 2 pools: 1 bounded pool for CPU-heavy tasks and 1 unbounded pool for IO tasks. I'll probably change the IO-pool to using virtual threads now, while keeping the CPU-pool on real thread.


Now would be a good time for an revised edition of JCIP (Java Concurrency in Practice).


For sure, yes, it's from 2006! I didn't have that one to hand, but remember the O'Reilly "Java Threads" book from 1999 and >50% of that was still useful for bringing a younger colleague up to speed.


Looking forward to Loom so that I can stop having to work on these async codebases. I have no idea why people looked at nodejs and thought it was a good framework to copy. (Sure maybe there is some high performance computing unlocked but most people dont need it, and most teams end up with a rats next of complexity)


Vert.x?


Lol you guessed it. I actually like vertx over spring in general. the rxjava is the real problem.


I love Vert.x and I actively use it right now. I actually like what they did with the Future [0] and Promise [1] interfaces, although there are a bunch of confusing (to me at least) aspects with the .map and .compose methods.

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


Even in Node I found the rx* libraries to be a bad abstraction and slow. I tend to just have my own observable stuff as I only want a small feature set in that regard anyway.


Yes same. I write myself a nice typed pipe() [2], and a one-after-the-other promise queue[4] whenever I need it but that's it really. I use web streams a lot.

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.

[1] https://effect.website

[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)); }

[5] https://GitHub.com/wiselibs/river



But rx comes out of the .net world, not from node


Recent and related:

Java 21: What’s New? - https://news.ycombinator.com/item?id=37067491 - Aug 2023 (255 comments)


I'm glad Java is returning to its original design with userland threads.

Java users can finally have some decent readable concurrent I/O without the blight of reactive.

This is a huge win.


That's not what Loom is. It uses a combination of native threading and user threads to create a hybrid mode that gives the benefits of both.


That sounds like Solaris threads, with userspace threads multiplexed on OS threads of course themselves multiplexed onto physical cores?

(https://a.co/d/1XGdjWQ is an Amazon link to Solaris Internals)


Pretty much. The way it is integrated is pretty novel though. It has special cases for the various low level networking APIs which uses the asynchronous versions of said APIs. This lets the threads block in a very efficient way and scale to 1M threads without much overhead.

In general the overhead of a thread is around 1kb compared to the 1mb system thread overhead. That's fantastic.


C# introduced async/await like 20 years ago and java still doesn't have it yet... I hope in the next 20 years they will finally find time to implement this feature.


The feature that required David Fowler from ASP.NET team to write a set of guidelines because everyone still uses it wrong, and had the team experiementing with Go and Java approach during last Summer, and they aren't adding it because it is too complex to retrofit?


Thank god. Async/await is an okay tradeoff for Rust, which lacks a managed runtime, but it is simply a bad abstraction for managed languages, where the runtime can automagically do the right thing for you without user involvement.


async/await is syntactic sugar for futures/callbacks/statemachines in .net. kotlin does this for coroutines on the jvm.

I think green threads are a much better was to solve the problem as its much easier to reason about and debug.


I trying to decide between Kotlin and Java 21. Please suggest me the advantages of Kotlin over Java 21.


Kotlin all the time, for these reasons:

* 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.


If Android, then Kotlin, else Java.

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.


How do they relate to Java’s records?


They do have similarities, but data classes actually predate records. One can declare a data class a record by using the @JvmRecord annotation, which probably introduces some JVM benefits in how records are represented in the Java memory model.

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.


I'm a bit out of the loop on new Java features so I was reading https://en.wikipedia.org/wiki/Java_version_history

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.


The safety isn't in avoiding backticks, that's just syntax. The safety comes from requiring you to specify which template processor you're using for the template string, so it's just as easy to use the right processor (eventually, SQL."") as it is to use the wrong one (STR."").

Using STR."" instead of STR.`` is just an aesthetic choice, and one that I think makes sense for Java.


Backticks are used as literals in a lot of contexts, especially DBs.

Overloading that behavior by default would likely blow up a lot of code and certainly violate the principle of least surprise.


By DBs, you mean MySQL :/

No one else has such an oddity


SQLite also features backticks as literals (though they're not the main option).

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).


I consider them to be odd when ANSI SQL has perfectly workable identifier quoting.

Different for the purpose of being different.


yes and mysql has an enormous install base.


will jdk21 present any opportunities for improvement in Clojure? like any part of core.async's implementation for example?


Can someone please explain how Project Loom and virtual threads in the JVM affect Kotlin’s coroutines?


If you think about how you use os threads today, you have two main dispatcher pools, default (cpu), and IO (for IO processing). You can now swap out the discrete IO threads with a virtual thread pool instead. You can get the "infinite" IO (blocking) task benefits of virtual threads like normal java virtual thread pools while still using the same benefits of structured concurrency that coroutines grants.

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?


Here's a take from Roman Elizarov (Kotlin bias) - https://www.youtube.com/watch?v=zluKcazgkV4

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.


Yeah, I watched that talk (and many others from KotlinConf) but it was a bit too high level and not actionable.


It largely doesn't affect Kotlin's coroutines. Kotlin targets platforms where Loom is not available (e.g. Android, native.)

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.


They are married to Android, naturally everything that doesn't exist on Android will hardly make it into Kotlin.

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.


Features can be found here: https://openjdk.org/projects/jdk/21/


No autoproperties yet?


    record A(B b) {}


It likely will never happen, properties based on naming conventions is a misfeature according to the design team (I have a field named IP, should it be getIp, getIP, or what?)

If you want a data-only object, use records.


Features listed here: https://openjdk.org/projects/jdk/21/


Thanks, we've changed the URL to that from https://mail.openjdk.org/pipermail/jdk-dev/2023-August/00805....


Beside legacy code, in what context would you start a new project un java?


I'm building a webapp backend in Python right now and the ecosystem compared to Java is just horrendous.

* 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


The bad news is the initial build is peak of the experience with Python - it's all downhill after that, since Python and other dynamic languages are mainly optimised for code creation rather than code maintenance.

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.


Having coded in both Python and Java I have to agree with your assessment. The things that people criticise Java for tend to be things that make sense when thinking about scripting or small programs, but as things scale up, being pedantic can be a benefit. I'd rather inherit 100k lines of Java than Python, that's for sure.


If you want to be productive in Python writing web apps you really should consider Django + django-ninja + PyCharm.


These are issues that I fear I will have moving from C#/.NET to Python. I know there are libraries like Pydantic for typing, but I think the tooling around C# is going to be tough to lose. That being said, the syntax and libraries are a huge draw for using Python.


Wait until you hit the terrible performance of Python. C#/.NET will outperform Python every single day.


Any system that requires scale and performance and in-depth monitoring. The one thing about Java that most people miss is the javaagent based instrumentation that is exemplary. So when you’re in trouble you always have jconsole . There’s an excellent APM that we use called glowroot which is open source.

Also what all others have mentioned below.


I would still pick Java any day over the vast majority of languages. Gigantic ecosystem, gigantic community, gigantic knowledge base, solid IDEs, and the list goes on.


World class garbage collectors. Well polished concurrency story (which gets a lot better with Java 21). Amazing JIT. Humongous mature ecosystem, larger than most languages. And the language (as of late) has been rapidly evolving to pick up new features and a trimmer runtime.


You had me after the first three words


"world class garbage" ?


Yep, and Java's the man for the job


High performance, typesafe code that can be maintained by a large team. Also has a very well established ecosystem of libraries, and multiple languages to choose from (Java, Scala, Kotlin, Clojure, etc)


Projects where you need mature boring technology so that you can focus on building your product.


When your CV already has enough shiny new stuff in it, and you just want to get the product built on a stable, future-proof platform.


Any project in which you don't want to be obsolete in 6 months.


When your engineering team is experienced and proficient in Java and your organization has built substantial tooling to support it.


what kind of tooling?


Any tooling you could imagine for a language java has that + 10 other implementations. Static analysis, debuggers, profilers, telemetry integration, metrics integration, etc. And these are GOOD. Java tooling, because it's pretty old at this point, is fairly well polished.


lots of java shops have loads of internal libraries that solve organizational goals or interface with other internal systems. they will continue to build new projects in java because the cost of tossing all that out is high.


When you agree that Scala's adoption is stagnant and Kotlin's advantage over JDK21 is moderate.


in retrospect, do they both seem to have been test scenarios for features subsequently picked up by Java?


JVM has the largest and highest quality ecosystem of libraries of any platform.


When you work in a company that requires reliable software


I'd pick Java any day for a new project.

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.


That’s a wrong question to ask. The proper one is the reverse I believe, why would I throw away one of the top 3 languages for a likely much smaller one with barely-existing ecosystem and questionable benefits?


[flagged]


Please don't post flamebait. We're trying for something else here.

https://news.ycombinator.com/newsguidelines.html


Ever heard of Android? It's not JVM per se (it's ART), but given your comment I'm pretty convinced that you don't know the difference anyway.

I would take JVM every day instead of those crappy web-based ElectronJS apps.


Official Android language is Kotlin though.


Kotlin is an official language, albeit the "preferred" language. Java is still very much supported and viable (outside of Compose).


OK, preferred language. I don't think that changes the validity of the point I was making.


Boomers are in their 70s. They're not everyone who is older than you.


Python is actually ~5 years older than Java.


> Boomers are in their 70s.

59-77, this year, by the usual definition.


Boomers born 20 years after WWII? I feel this is Gen X erasure. I barely count as Gen X and I'm almost 50.


> Boomers born 20 years after WWII?

Not quite 20, the usual years for Boomers are 1946-1964.

> I barely count as Gen X and I'm almost 50

Gen X is 1965-1980, if you were 50 (I am) you’d be in the latter half of Gen X. If you are merely “almost 50” you're late enough, or nearly so, to be counted in the Xennial / Oregon Trail subgeneration at the cusp of Gen X and Millenial (usually counted as starting somewhere 1975 and 1977.)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: