4 ms·
It's not news only if you carefully read that JEP (and even then, it's misleading, it says "Pinning does not make an application incorrect, but it might hinder
by cesarb 3y ago
It's not news only if you carefully read that JEP (and even then, it's misleading, it says "Pinning does not make an application incorrect, but it might hinder its scalability" as if using "synchronized" could only lead to not getting the full performance advantage of virtual threads; there's no mention of potential thread starvation or deadlocks). But most people are not reading JEP pages; most people are getting their information from release notes and news articles, and AFAIK these didn't mention the important caveat that virtual threads can misbehave if anything waits within a synchronized block (which is very common, synchronized blocks and methods exist and are widely used since the early days of Java).
For instance, a quick web search led me to a release notes page (https://www.oracle.com/java/technologies/javase/21-relnote-issues.html https://www.oracle.com/java/technologies/javase/21-relnote-i...) and a news article (https://www.infoworld.com/article/3689880/jdk-21-the-new-features-in-java-21.html https://www.infoworld.com/article/3689880/jdk-21-the-new-fea...), neither mention anything about synchronized blocks and methods. In fact, the later one says "Previously previewed in both JDK 20 and JDK 19, virtual threads will be finalized in JDK 21.", as if everything now works as expected with virtual threads on Java 21. Which is clearly not the case, if synchronized blocks are enough to make it misbehave. IMO, we can only say virtual threads are really "finalized" once synchronized blocks work as expected (releasing the carrier thread, like AFAIK ReentrantLock already does) when running on a virtual thread.
- karmakaze 3y agoUsing "synchronized" by itself and calling anything has the potential thread starvation or deadlocks. Virtual threads only makes it worse. Kotlin has mutexes that presumably are compatible with coroutines: kotlinx.coroutines.sync.Mutex Until Java sorts this out, it could be its "Rust async" watershed moment.
- DarkNova6 3y agoYou can use Concurrent Locks from the standard library. And it has been communicated that you should not be using low level concurrency constructs for years. But people just… don’t.
- cesarb 3y ago> And it has been communicated that you should not be using low level concurrency constructs for years. Where has this been communicated? The first I heard that you shouldn't use synchronized statements or blocks was in these virtual threads discussions; and the popular IDE I use for Java at work doesn't flag synchronized methods or blocks as something to be avoided. The only thing I had heard about it was that you should avoid synchronizing on objects exposed to users of your library, since it would make it possible for code outside your control to also synchronize on that same object (but IMO synchronizing on arbitrary objects you got from somewhere is itself a bad practice, you should only synchronize on objects you control). And obviously, synchronized methods or blocks do not have the "fair" mode, which might be necessary (or good) in some cases. (And before Java 9, stack overflows within the implementation of some concurrency constructs could lead to unexpected behavior, see https://openjdk.org/jeps/270 https://openjdk.org/jeps/270 for details; this was never a problem with synchronized methods or blocks.)
- charcircuit 3y agoI started Java over a decade ago and by then no one was writing synchronized blocks when doing multithreaded code.
- jiggawatts 3y agoYou’re being downvoted, but I haven’t done Java programming in over twenty years and even I knew that!
- CHY872 3y agoI've been coding in Java for a bit longer than that, and in my memory it's always been known that ReentrantLock is _faster_, but synchronized has better language level support (as in, the language won't let you forget to unlock, whereas with locks you need to remember your finally statement). And then, unlike ReentrantLock, synchronized uses no extra memory, which is good for things that end up being uncontended, and then in both cases, the performance doesn't matter if the lock is uncontended, which most of mine were. There of course are things like ErrorProne that statically check that you unlocked your lock, but there's still bugs possible.
- CHY872 3y agoI think this one's weird because the language puts so much effort into making it hard to hit this that you won't notice until it's a gigantic problem. For me, this was enough of a problem that I wrote a Java agent which patches the method which blocks the carrier thread to just throw (ByteBuddy makes it easy!). Like, it makes sense that when you synchronized on an object, there's room for contention. It seems weird that synchronizing on an _uncontended_ object can cause contention. But that's what this is. The behaviour is that you have target numCores carrier threads, and if someone synchronizes on an object and then does a non-blocking sleep, it's now blocking, because synchronizing upgraded the non-blocking I/O to blocking. So basically when you hit this issue, it's because not only has the bad thing been happening, it's also been happening badly enough that all the compensation mechanisms have failed. It's just weird that a whole language level keyword behaves this badly.