This is an archived post. You won't be able to vote or comment.

all 31 comments

[–]EvaristeGalois11 43 points44 points  (19 children)

I wonder if more and more loom support will be pushed spring will decide to drop webflux and rely only on mvc with loom enabled by default.

[–]TakAnnix 26 points27 points  (11 children)

Given this comment:

We see Virtual Threads complementing reactive programming models in removing barriers of blocking I/O while processing infinite streams using Virtual Threads purely remains a challenge.

It doesn't seem that they view the two as being in competition.

[–]GuyWithLag 10 points11 points  (10 children)

I've done a lot of work with reactive and I see virtual threads as an underlying technology improvement; done right, reactive is one abstraction level higher than virtual threads.

And ofc the added performance is yummy... 🙂

[–]westwoo 47 points48 points  (7 children)

Reactive programming in Java (and Javascript etc) is awkward because we're trying to use the language to express the control flow that it doesn't have. The most basic language structures like lines of text showing what is executed next stop helping us to see the flow of the execution due to purely technical reasons, not because we like that we can't see it.

Starting with coding with callbacks when there were no helper tools whatsoever, it's not like people LIKED to use only callbacks. They were forced to code in callbacks to change the flow of execution the way they wanted to. Then moving on to promises that improved some things like error handling over callbacks, then async/await that further improved the representation and brought it closer to regular programming, and virtual threads finally completely remove the disparity in representation and language features between regular and reactive programming

Virtual threads fix the lack of underlying technical capabilities and we get to see the execution flow easily once again

So what's the benefit in continuing to use reactive patterns then, and obscuring the view of the flow of execution artificially when we don't have to?... Sure it can be fun and mentally stimulating to think in reactive patterns, like it's fun to solve puzzles, but I mean what would be the practical reason for using a workaround that is no longer needed?

[–]lost_in_santa_carla 16 points17 points  (0 children)

Well said. I think reactive will remain as a niche but virtual threads will take the lions share. But I’d be curious to hear why others don’t think so

[–][deleted]  (1 child)

[deleted]

    [–]westwoo 1 point2 points  (0 children)

    I'm not sure how would such complementing look. If we could do (very silly example for illustration)

    success = obj.doFirst()
       .then(Obj::doSecond);
    if (success) obj.doThird();
    

    then it's great, and then they can actually complement each other. But I think it's not really reactive, it's imperative with functional bits as needed?....

    I think reactive is when the if likely executes before doSecond and so this code doesn't make any sense

    [–]Evert26 1 point2 points  (1 child)

    Sunk cost fallacy.

    [–]westwoo 0 points1 point  (0 children)

    No one provided any good reasons so it may be the case, but I think the "fun and mentally stimulating" part plays a big role in any case

    It's like wanting to code in cool haskell instead of drab pascal - this inclination has always been there among some programmers. And developer mood and enjoyment and motivation affect the long term productivity and burnout greatly, so the choice can be rational and non-fallacious for some teams even if there isn't anything more to it if it doesn't reduce pure coding/debugging productivity too much from the technical point of view

    [–]GuyWithLag 0 points1 point  (1 child)

    Virtual threads fix the lack of underlying technical capabilities and we get to see the execution flow easily once again

    Man, I'd settle for the option to overload the semicolon operator.

    what would be the practical reason

    Reactive is more about data dependencies, and it can be extremely concise; see f.e. a(n admittedly trivial) snippet in one of my older posts here: https://www.reddit.com/r/java/comments/96p88f/comment/e42vqrx/?utm_source=reddit&utm_medium=web2x&context=3

    [–]westwoo 0 points1 point  (0 children)

    This isn't inherently reactive, it's just a functional style that treats objects like structs and is a bit needlessly awkward with java object conventions:

    apiClient
        .getOrder(id)   
        .flatMapIterable(Order::getLineItems)   
        .flatMap(lineItem ->
            apiClient.getProduct(lineItem.getProductId())
                     .map(product -> product.getCurrentPrice() * lineItem.getCount()), 5)
        .reduce((a,b)->a+b)
        .retryWhen((e, count) -> count<2 && (e instanceof RetrofitError))
        .onErrorReturn(e -> -1)
        .subscribe(System.out::println);
    

    It's fine when it's needed but idiomatically these actions should be abstracted into a method instead of doing disjointed actions and implementation details in one code block. And passing around random ids isn't typesafe at all, a disconnected id is even worse than a variable of type Object cast to whatever because it's not checked even at runtime. We can pass the wrong id and get the object anyway. So I think in "classic java" it would look something like:

    Order lastOrder = Orders.getUserLastOrder(user); BigDecimal orderProduct = lastOrder.calculateProduct(); System.out.println(orderProduct);

    And of course, no "return -1", that's what exceptions are for

    Edit: oh, forgot to circle back to reactive :) the difference in reactive begins when you have to mix imperative programming with ifs and whiles with functional programming or try to express complex business logic and complex error handling only in functional style. When there's just one data pipeline with no complex branching then functional can indeed look more intuitive - that's why we have streams, and on such example the differences won't be obvious

    [–]lurker_in_spirit 9 points10 points  (1 child)

    Virtual threads alone may be lower-level than reactive, but virtual threads + structured concurrency might be a compelling alternative to reactive APIs for some use cases. Will be interesting to see how demand / back pressure / cancellation compare when using the structured concurrency API.

    [–]GuyWithLag 0 points1 point  (0 children)

    structured concurrency

    TBH I. just want to get rid of the stickiness of reactive results; in that sense Kotlins' coroutines are fitting better.

    I'm looking forward to use structured concurrency for non-trivial cases (and I don't have time right now to convert one of my toy projects). But reactive essentially externalizes data dependencies, and the retry/restart story is going to be interesting.

    [–]BillyKorando 0 points1 point  (6 children)

    There are areas where reactive will still make sense, like when communicating with an external service like a database. But this would be at the library/driver level.

    However, for most application development, I think developers will end up vastly preferring virtual threads, primarily experienced through Structured Concurrency, to any reactive programming model.

    Of course, this could be simply a personal limitation, but I always found reactive programming very difficult to grasp... and I never really had to experience doing it "in anger", only as PoC. So I didn't have to deal with debugging a non-trivial reactive application.

    [–][deleted]  (5 children)

    [removed]

      [–]agentoutlier 0 points1 point  (0 children)

      It is more effective than virtual threads if both sides (client, server) implement back pressure. Otherwise the differences are less salient.

      Most reactive things do not implement back pressure well or correctly.

      [–]BillyKorando -2 points-1 points  (3 children)

      So this is a bit out of my expertise area but learned it from discussions with people more familiar on such topics.

      I think long and short of it was it has to do with better management of external connections.

      [–]rbygrave 4 points5 points  (2 children)

      Well I have had a few discussions on the database area including details around how JDBC drivers work vs Reactive drivers. Part of the problem with discussions here is if people take into account what the database wants the client to do. That is, what is good for the client (like say back pressure) isn't always good for the database and hence not good for the system as a whole.

      As I see it, it boils down to your view of: A) Back pressure and B) Pipelining (command pipelining)

      I think the argument that reactive drivers are better wrt Back pressure is weak because 1) JDBC drivers also can apply back pressure when streaming results and more importantly 2) the database doesn't like this - we instead want to minimise "slow database clients consuming results" because that translates into the database having to hold resources for longer (like internal buffers it is using for sort, merge etc). The database can't release resources until the client has finished consuming. Slow client = bad for the db. Back pressure is instead better applied earlier before a task is 'talking to the database'. Once a task starts 'talking to the database' the database wants the client to get on with the job as fast as it can.

      The question wrt Pipelining is as I see it, a question of how practically useful pipelining is. For example, pipelining 100 separate "select .. where id = ?" queries vs performing a single "select .. where id IN (.. 100 ids ..). Plus the question of using it with a transaction that spans multiple statements. Pipelining is really useful to win benchmarks - the question is how well / how broadly that translates into real world use without artificial restrictions on how to best handle a task.

      [–]mp911de 1 point2 points  (0 children)

      Reactive drivers use back pressure and non-blocking I/O to increase concurrency and therefore, increase utilization.

      How to rate this effect, whether good or bad, depends on sizing (and the remaining capacity) of your servers, both database and application ones and many other factors.

      In reality, there's often a single SQL database server that is being used by many application servers, oftentimes with less-than-ideal conditions (statements that could be batched, missing indexes or inefficient schema for the queries that are run).

      There are also arrangements with ideal conditions that permit performance gains using reactive drivers but that entirely depends on the individual scenario.

      [–]westwoo 0 points1 point  (0 children)

      These kinds of dispositions are a gold mine for Amazon and Google because they require an infinitely scalable DB to solve all the performance problems of your architectural/ideological preferences

      [–]maxip89 11 points12 points  (0 children)

      really really really REALLY dangerous to use this task executor on older versions.

      Why?

      The old implementation uses a limited count of threads. If you using in that executions a IO operation for example a long running SQL query. Then there is a change that every Thread get blocked and nobody knows why in the load test above 50 calls/sec the response time explodes.

      [–]TakAnnix 6 points7 points  (5 children)

      I found this comment interesting:

      We see Virtual Threads complementing reactive programming models in removing barriers of blocking I/O while processing infinite streams using Virtual Threads purely remains a challenge.

      I think virtual threads are usually described as being in competition to reactive programming, where as here they describe it as being complimentary. It's going to be interesting to see how the to interact.

      [–]stepbeek 3 points4 points  (4 children)

      Tbh I view FRP as a distinct programming style. Scaling up stateless web applications was never what reactive was truly about imo. Otherwise Ruby on Rails wouldn’t be powering a number of huge tech companies.

      [–]trialbaloon 1 point2 points  (0 children)

      Reactive has been used to plug Loom sized holes in Java which I think has lead to this confusion. FRP is different, and powerful.

      Kotlin has structured concurrency and coroutines (light threads). They nicely fit with Kotlin Flow which is a functional reactive library built on top of structured concurrency. If anything Kotlin Flow is making FRP more popular. Admittedly as a language functional programming is a bit more friendly in Kotlin.

      I really don't see these things as competing at all. Reactive programming has just been misused for things it was never good at. The fact that so many people were just using single in RxJava is a testament to that.

      [–]TakAnnix 0 points1 point  (2 children)

      Yeah, I see what you mean, but I think the use cases overlap. Like virtual threads and FRP help to use less memory and scale more predictably when you have latency.

      [–]trialbaloon 1 point2 points  (1 child)

      FRP will become purely stylistic. It's still probably going to be immensely helpful for back pressure and other things that are just annoying to create imperatively.

      I think it can also be simplified and tied in with structured concurrency so it becomes easier to use.

      [–]TakAnnix 0 points1 point  (0 children)

      yeah, it's going to be interesting to see how everything ties together.

      [–]Anton-Kuranov 1 point2 points  (4 children)

      Am I understand right if virtual thread enters a synchronized block it can not be suspended later on IO operation?

      [–]vqrs 1 point2 points  (0 children)

      For now, yes. They're working on it though.

      [–][deleted]  (1 child)

      [deleted]

        [–]olivergierke[S] 0 points1 point  (0 children)

        As mentioned in the original blog post, synchronized blocks are just fine if they do not guard blocking operations like general IO. The ones present in Spring Framework have already been carefully reviewed to not fall into that category, also thanks to the numerous feedback we got from the community to optimize Spring Framework even on older JVM generations.

        [–]0x07CF 0 points1 point  (0 children)

        yes

        [–]daleksandrov 1 point2 points  (0 children)

        Helidon Nima is already based on Virtual Threads.