Hacker News new | ask | show | jobs
by LinXitoW 695 days ago
From my very limited exposure to virtual threads and the older solution (thread pools), the biggest hurdle was the extensive use of ThreadLocals by most popular libraries.

In one project I had to basically turn a reactive framework into a one thread per request framework, because passing around the MDC (a kv map of extra logging information) was a horrible pain. Getting it to actually jump ship from thread to thread AND deleting it at the correct time was basically impossible.

Has that improved yet?

3 comments

I faced this issue once. I solved it by creating a wrapping/delegating Executor, which would capture the MDC from the scheduling thread at schedule-time, and then at execute-time, set the MDC for the executing thread, and then clear the MDC after the execution completes. Something like...

    class MyExecutor implements Executor {
        private final Executor delegate;
        public MyExecutor(Executor delegate) {
            this.delegate = delegate;
        }
        @Override
        public void execute(@NotNull Runnable command) {
            var mdc = MDC.getCopyOfContextMap();
            delegate.execute(() -> {
                MDC.setContextMap(mdc);
                try {
                    command.run();
                } finally {
                    MDC.clear();
                }
            });
        }
    }
What do you mean by hurdle? ThreadLocals work just fine with virtual threads.
It's not recommended though.

See https://openjdk.org/jeps/429

If you keep ThreadLocal variables, they get inherited by child Threads. If you make many thousands of them, the memory footprint becomes completely unacceptable. If the memory used by ThreadLocal variables is large, it also makes it more expensive to create new Threads (virtual or not), so you lose most advantages of Virtual Threads by doing that.

I don't think that's correct. ThreadLocals should behave just like on regular OS threads, the difference is that you can suddenly create millions of them.

You used to be able to depend on OS threads getting reused because you were pooling them. You can do the same with virtual threads if you wish and you will get the same behavior. The difference is we ought to spawn new threads per task now.

Side note, you have to specifically use InheritableThreadLocal to get the inheritance behavior you speak of.

If you are already in a reactive framework, why would you change to virtual threads? Those frameworks pool threads and have their own event loop so I would say they are not suitable for virtual thread migration.
Yes, if you're happy with the reactive frameworks there's no reason to migrate. Most people, however, would love to remove their complexities from their code bases. Virtual Threads are much, much easier to program with. There's downsides, like not being able to easily limit concurrency, having to implement your own timeout mechanisms etc. but that will probably be provided by a common lib sooner or later which hopefully provides identical features to reactive frameworks, while being much, much simpler.
I've not looked too deeply. We use the eventloop model, and we're guaranteed that data is only mutated by a single unit of work at a time which means you don't need to use any concurrent data types, volatile etc. This is great for micro performance.

Does the same apply to virtual threads?

Edit: I think I answered my own question. Java virtual threads have the same memory model as regular java threads so yes, I need to use the same semantics. That rules replacing the eventloop model for us.