10 ms·
Guide to Java Virtual Threads
- vijucat 4y agoC# .NET had async and await in 2012, for comparison. I've always loved Java but Microsoft deserves immense credit for raising the bar, and so quickly, too.
- VagueMag 4y agoAt least from the Java language advocates' perspective, async/await is a worse solution to the problem of async than the structured concurrency approach that virtual threads will enable.
- andrekandre 4y agothats interesting, any good examples?
- VagueMag 4y agoTons of writing and debate on this subject but the canonical starting point is "What Color Is Your Function?" https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
- cyberax 4y agoAsync/await are also much slower than properly implemented (see: Golang) fibers. Basically, with async/await you need to allocate on the heap frames for each level of the async/await call stack. You basically get a linked list of stack frames. This is essentially isomorphic to segmented stacks that Go ditched in favor of resizable stacks, because segmented stacks kill performance if a tight loop just happens to cross a segment boundary.
- DeathArrow 4y ago>Async/await are also much slower than properly implemented (see: Golang) fibers. Are there any benchmarks to back that affirmation?
- cyberax 4y agoHm. I know this from my personal work, replacing Go code with an async/await code. There's a bunch of benchmarks that compare C# async/await and Goroutines (e.g. https://alexyakunin.medium.com/go-vs-c-part-1-goroutines-vs-async-await-ac909c651c11 https://alexyakunin.medium.com/go-vs-c-part-1-goroutines-vs-... ). Go still wins there, but they all miss the very important case of nested async/awaits. These microbenchmarks also focus on looking at Go channels versus other synchronization methods, which in my experience is not that relevant (Go channels just suck). I guess I'll need to write a blog post.
- DeathArrow 4y agoThose benchmarks are really old, things can change quite fast when it comes to programming language implementations.
- cyberax 4y agoI wrote a quick-and-dirty benchmark: https://blog.alex.net/benchmarking-go-and-c-async-await https://blog.alex.net/benchmarking-go-and-c-async-await Results: Go - 32ms, C# - 494ms. The difference also becomes bigger as you add more recursion levels.
- dgb23 4y agoAsync/await proliferates. If you call await, your function must be async too. Green threads are decoupled from synchronization.
- DeathArrow 4y agoYou can use tasks without async/await.
- DeathArrow 4y agoasync/wait is using tasks which are the equivalent of virtual threads. What I do like to see in C# is something akin to goroutines from Go or actors in Elixir.
- shellac 4y agoThis response confuses me since goroutines and Erlang processes are really very similar to project Loom's virtual threads. The mistake is that async/await is not equivalent to virtual threads: the former can do only a subset of what the latter can.
- guiriduro 4y agoVirtual actors for C# are offered by solutions like MS Orleans as I understand it.
- masklinn 4y ago> What I do like to see in C# is something akin to goroutines from Go or actors in Elixir. A task is equivalent to a goroutine.
- DeathArrow 4y agoNot really, see: http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y... https://alexyakunin.medium.com/go-vs-c-part-1-goroutines-vs-async-await-ac909c651c11 https://alexyakunin.medium.com/go-vs-c-part-1-goroutines-vs-... https://stackoverflow.com/questions/70501171/are-golang-goroutines-relatively-the-same-as-c-sharp-tasks https://stackoverflow.com/questions/70501171/are-golang-goro...
- dopidopHN 4y agoMy understanding is that green threads are different on the implementation side as well. Basically they are cheaper at the system level? The API do not even change much last time I checked. It’s all happening in how the JVM span threads
- seabrookmx 4y agoThat head start has had a huge impact on the ecosystem too. Random libraries (ex: Google.Cloud.PubSub.V1) have first class, mature support for async and streams. Compare that to Python (and I'd have to assume Python is much more popular on Google Cloud) which only recently got async support and it's still kludgy. This really applies across the board for anything web related. JavaScript/TypeScript is probably the only ecosystem with comparable async support, unless you count golang which achieves a similar result with different ergonomics. I'm still on the fence on which I personally prefer.. I can see the appeal of not having the async logic pollute the callstack, but at the same time the magic[1] way golang handles i/o seems antithetical to its philosophy of being simple and explicit (for example, with respect to error handling). [1]: https://www.reddit.com/r/golang/comments/xiu4zg/comment/ip77rg1 https://www.reddit.com/r/golang/comments/xiu4zg/comment/ip77...
- capableweb 4y agoJava Virtual Threads seems to be a ("better" in their mind) alternative than async/await, so not sure Microsoft should be credited for Java Virtual Threads? > Also, the async/await approach, such as Kotlin coroutines, has its own problems. Even though it aims to model the one task per thread approach, it can’t rely on any native JVM construct. For example, Kotlin coroutines based the whole story on suspending functions, i.e., functions that can suspend a coroutine. However, the suspension is wholly based upon non-blocking IO, which we can achieve using libraries based on Netty, but not every task can be expressed in terms of non-blocking IO. Ultimately, we must divide our program into two parts: one based on non-blocking IO (suspending functions) and one that does not. This is a challenging task; it takes work to do it correctly. Moreover, we lose again the simplicity we want in our programs. > The above are reasons why the JVM community is looking for a better way to write concurrent programs. Project Loom is one of the attempts to solve the problem. So, let’s introduce the first brick of the project: virtual threads.
- jayd16 4y agoIt's not really an alternative to async/await. Implicit vs cooperative multithreading have pros and cons for each.
- capableweb 4y agoAuthors of Loom seems to disagree with you. > An alternative solution to that of fibers to concurrency's simplicity vs. performance issue is known as async/await, and has been adopted by C# and Node.js, and will likely be adopted by standard JavaScript. Continuations and fibers dominate async/await in the sense that async/await is easily implemented with continuations (in fact, it can be implemented with a weak form of delimited continuations known as stackless continuations, that don't capture an entire call-stack but only the local context of a single subroutine), but not vice-versa. > While implementing async/await is easier than full-blown continuations and fibers, that solution falls far too short of addressing the problem. While async/await makes code simpler and gives it the appearance of normal, sequential code, like asynchronous code it still requires significant changes to existing code, explicit support in libraries, and does not interoperate well with synchronous code. In other words, it does not solve what's known as the "colored function" problem. This is under the section called "Other Approaches" in https://cr.openjdk.org/~rpressler/loom/Loom-Proposal.html https://cr.openjdk.org/~rpressler/loom/Loom-Proposal.html That seems to indicate to me that async/await would be an alternative to virtual threads.
- thatnerdyguy 4y agoAnd they are experimenting with green threads now. Would be... hilarious? If it landed in .NET before Java. https://github.com/dotnet/runtimelab/tree/feature/green-threads https://github.com/dotnet/runtimelab/tree/feature/green-thre...
- geodel 4y agoIn Java it is landed already as preview feature last September and will be final feature this September.
- pkulak 4y agoThis is way better than async/await. This is Go/Erlang levels of green thread ease of use.
- metaltyphoon 4y agoI guess everyone is entitled to their opinion.
- riku_iki 4y ago> .NET had async and await in 2012, for comparison. java had ExecutorService and futures support for a while, it is just another synthetic sugar around the same solution, although Java's approach is much more powerful and flexible.
- kitd 4y agoJava had green threads in v1.0.
- DeathArrow 4y agoNot only async, but C# had LINQ, lambdas, records, pattern matching, pointers, hardware instructions via intrinsics, stackalloc etc. before similar constructs came into Java, if they ever did. Probably there are examples where Java introduced something first, but I don't know because I'm not so well versed in Java. While similar and inspired by Java, I do prefer C# because is less verbose, requires less boiler plate, it generally has only one proper way to do things and is kind of jack of all trades, in the sense you can tackle any area of programming besides very low level systems programming - and It quite can reach that point, too if there will be a way to disable GC and allow manual memory management. Web backend - check, web frontend - check, mobile apps - check, desktop - check, multi platform - check, embedded - check, games - check, VM - check, native AOT - check. It also looks great in benchmarks: https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/csharp.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/... I am biased but since I recognize it, maybe you shouldn't downvote me just for that. :) And what's even better than C# is F#, but that's too bad nobody likes functional programming or hire programmers to use functional languages.
- krzyk 4y ago> Not only async, but C# had LINQ, lambdas, records, pattern matching, pointers, hardware instructions via intrinsics, stackalloc etc. before similar constructs came into Java, if they ever did. Just a clarification regarding one statement which I know for sure is not true: Java team started working on records before C# started similar work. I don't know about the rest.
- shellac 4y agoAnd pattern matching was started in java first (goes hand-in-hand with records to some extent).
- pron 4y agoThere's plenty of stuff Java did first (or only) -- an optimising JIT, two new generations of GC (G1, ZGC--a low latency GC with <1ms max pause time), low-overhead deep profiling (Java Flight Recorder) -- but they're all in the runtime. Java's strategy since the beginning has always been to innovate on the platform and be a last-mover on the language, keeping it conservative. .NET seems to follow an opposite strategy. That's how we've been able to avoid properties and just have algebraic data types, avoid async/await and do virtual threads, avoid string interpolation and just have safe string templates. This also allows us to keep the number of features in the language relatively low -- as we, and most of our users, like it.
- recursivedoubts 4y agojava green threads strike back
- jjtheblunt 4y agoThat would be the best name by far!
- rzzzt 4y agoThey were called green as well as "M:N" threads: https://en.wikipedia.org/wiki/Green_thread#Green_threads_in_the_Java_virtual_machine https://en.wikipedia.org/wiki/Green_thread#Green_threads_in_...
- jjtheblunt 4y agohttps://a.co/d/2hw5NSv https://a.co/d/2hw5NSv Solaris Internals has the greatest explanation of this that I knew, including what you said.
- codr7 4y agoGreen used to mean non-preemptive; which is not the case here, or for Golang or Erlang (afaik). M:N is all about resource usage, the semantics are mostly the same as OS threads.
- jjtheblunt 4y agohttps://en.wikipedia.org/wiki/Green_thread https://en.wikipedia.org/wiki/Green_thread
- mrkeen 4y agoWeird for the article to compare OS threads to Loom threads, and skip over the last ten years of Futures.
- jjtheblunt 4y agobut that's what the topic of the article in its title is. I say that as someone who once had a Solaris Internals book (and worked at Sun in that area), and it would be pretty great for that book's explanation to be online for Java people who didn't use original green threads to see.
- gleenn 4y agoBut Futures still tie up a real OS thread. You can chain them together which alleviates some additional thread cost but it's not M:N scheduling, you're just being more clever with your OS threads. Virtual Threads separate those concepts.
- riku_iki 4y ago> but it's not M:N scheduling Why do you think it is not M:N scheduling? You have M tasks and N OS threads in the pool?
- gleenn 4y agoThere is an example in TFA which shows how if you have a normal thread pool and two tasks with one sleeping indefinitely and only a single thread to execute, the the second thread never executes. Then the example switches to virtual threads and both can execute (after also adding a sleep). This shows how you can have more tasks than threads and everything still can get a chance to run. That's not true with Futures prior to this. M:N means you can run more tasks in parallel than you have threads, just not with a speedup or more parallelism. So only virtual threads are M:N.
- riku_iki 4y agoI think what you are describing is API for manual scheduling: you call sleep() to give up control to scheduler. With tasks/futures you supposed to submit another task to pool which will wake up after certain time. Both are "green threads" with manual scheduler (you need to make explicit call to give up control to scheduler) but different API.
- wahern 4y ago> The problem with platform threads is that they are expensive from a lot of points of view. First, they are costly to create. Whenever a platform thread is made, the OS must allocate a large amount of memory (megabytes) in the stack to store the thread context, native, and Java call stacks. This is due to the not resizable nature of the stack. Moreover, whenever the scheduler preempts a thread from execution, this enormous amount of memory must be moved around. Scheduler pre-emption does not cause stack memory to be copied. Perhaps they're thinking of registers. (As an side, suspension and resumption of Java virtual threads does result in its stack being copied--saved and restored--as this was deemed less costly[1] than growable stacks, which is how Go works.) > As we can imagine, this is a costly operation, in space and time. In fact, the massive size of the stack frame limits the number of threads that can be created. We can reach an OutOfMemoryError quite easily in Java, continually instantiating new platform threads till the OS runs out of memory: Stack frame != stack. The author seems confused about some concepts. I didn't read beyond this so don't know whether that confusion effected any of their conclusions or advice. [1] EDIT: Whether less costly in terms of performance or development effort I'm not sure. A major reason JavaScript and many other languages don't implement stackfull coroutines is that the virtual machines--interpreters, JITs, etc--are written in a way that in-language function calls and recursion directly or indirectly rely on the underlying native "C" stack. This correspondence is not something you can typically remedy without completely rewriting the implementation from scratch. Language implementations like Go and Lua and were written from the beginning to avoid this correspondence. To accomplish stackfull coroutines languages like Java and, IIUC, OCaml really had no choice but to rely on some other tricks, though I think OCaml permitted some tricks not available to Java, because OCaml could do some transforms which Java couldn't given the nature of the JVM.
- intelVISA 4y agoPeople are worried about GPT4 producing nonsense huh... articles like this prove humans still have that market on lock.
- grimgrin 4y ago> The problem with platform threads is that they are expensive from a lot of points of view. First, they are costly to create. Whenever a platform thread is made, the OS must allocate a large amount of memory (megabytes) in the stack to store the thread context, native, and Java call stacks. This is due to the not resizable nature of the stack. Moreover, whenever the scheduler preempts a thread from execution, this enormous amount of memory must be moved around. There are a few issues and inaccuracies in this statement. While it is true that platform threads can be expensive in terms of resources, the claim that the OS must allocate "megabytes" of memory for the stack is an exaggeration. The actual size of the stack depends on the operating system and the specific implementation, but typical default values range from a few dozen kilobytes to a few hundred kilobytes, not megabytes. The statement implies that the entire stack is moved around when the scheduler preempts a thread from execution. This is not accurate. When a thread is preempted, the operating system saves the context of the thread, which is a relatively small amount of data, including the values of the CPU registers and the program counter. The stack itself is not moved around during this process. It is not correct to say that the stack is "not resizable." While the default stack size is set by the operating system, many programming languages and operating systems allow you to specify the stack size when creating a new thread. However, it is true that once a thread has been created, its stack size typically cannot be changed. Sent from OpenAI.
- throwaway2037 4y agoSorry to ask a somewhat unrelated question: How did the author create those flow charts? That style is Just. So. Cool.
- dgb23 4y agoLooks like they used excalidraw. It’s a OSS and a PWA. A lot of people seem to use it because it lets you draw without worrying about details.
- FlyingSnake 4y agoIt could also be an Onyx or ReMarkable tablet as well.
- Kolja 4y agoThe style, and especially the splotchy background coloring, made Paper[1] (By WeTransfer it seems? When it was released they were called fiftythree.) my first guess. [1]: https://apps.apple.com/us/app/paper-by-wetransfer/id506003812?platform=ipad https://apps.apple.com/us/app/paper-by-wetransfer/id50600381...
- jasmer 4y agoThese are great, but Loom discussions almost always mix details with issues of the API. It's hard to fathom what that means for end developers. Will anything at all change but performance? What are the practical implications beyond that? We just don't bother with Executors and live happily?
- twic 4y agoThe way i think about the situation is: 1. Platform threads + blocking APIs = comfortable to use, does not scale to many connections 2. Platform threads + non-blocking APIs = painful to use, scales to many connections 3. Virtual threads + blocking APIs = comfortable to use, scales to many connections So, at the moment, if you want to write software which scales to many connections (which not everybody needs to do, but some do), you have to suffer for it. With virtual threads, it will be a lot more pleasant. As a concrete example, with non-blocking architecture, there is no way to have an InputStream or OutputStream which streams an arbitrarily large amount of data over the network without buffering it all. Because the contracts of those classes say that they block when there is no data or space available! If you look at the APIs of web servers based on non-blocking APIs (eg [1], [2]), they want you to read or write the whole body in one go, or maybe in chunks, which will be pushed to you if you're reading. You can only build a stream on top of that by buffering everything, or using another thread, which destroys the advantage of non-blocking architecture. There is loads of useful I/O machinery that is built on top of streams, like JSON parsers and generators, so your choice is either not using that, or accepting buffering or extra threads. With virtual threads, you just use blocking I/O, servers and clients can expose streams for request and response bodies, and it's trivial to use any I/O machinery you like. It's also far easier to write your own. [1] https://undertow.io/javadoc/1.3.x/io/undertow/io/Receiver.html https://undertow.io/javadoc/1.3.x/io/undertow/io/Receiver.ht... [2] https://undertow.io/javadoc/1.3.x/io/undertow/io/Sender.html https://undertow.io/javadoc/1.3.x/io/undertow/io/Sender.html
- xxs 4y ago>2. Platform threads + non-blocking APIs = painful to use, scales to many connections Wow, I have written a substantial amount of non-blocking IO since around java 1.4.2 (when it actually became stable). It was not much harder to use at all (compared to io streams). The issues w/ buffering/scalability and internal scheduling will be exactly the same with green threads.
- DeathArrow 4y agoGreat, so Java has virtual threads 13 years after C# introduced tasks. Why can't Java introduce useful concepts faster?
- shellac 4y agoThe java equivalent of task is probably Future or CompletableFuture, which have been in java since 2012 / 2014. In terms of running them, Executors have been there since 2014. (Obviously this would have been 1:1 with OS Threads)
- pulse7 4y agoJava had green threads in Java 1.1 (February 1997), C# 1.0 was released in January 2002...
- Bjartr 4y agoI learned a new word from this article, "Omissis", which means "omissions and reactions". Equivalent in this context to "...snip..." or "some code omitted".
- rr888 4y agoCan I forget about reactive programming now? Its been fashionable for the last 5+ years, I hate it. Kinda cool to play around with but just seems to double the complexity on everything.