[concurrency-interest] CompletableFuture in Java 8

Josh Humphries jh at squareup.com
Thu Dec 4 23:30:13 EST 2014


Hey, Viktor. I think I've touched on some of this already. But, since you
said you're very much interested, I'll elaborate on my thinking.


Every decision is a trade-off. Mixing concerns can bloat the API and
increase the cognitive burden of using it, but it can also provide greater
functionality or make certain patterns of use easier. While the two
concerns we're discussing may seem similar, they are very different (at
least to me) regarding what they are actually providing to the developer,
so the trade-offs are different.


Concern #1: Exposing methods to imperatively complete the future vs. having
the future's value be provided implicitly (by the running of some unit of
logic). We're not really talking about mixing the two here. My objection
was that CompletionStage#toCompletableFuture leaks the imperative style in
a way that is simply inappropriate. So my objection here is about poor
encapsulation/abstraction. If the API had returned FutureTask, that too
would have been bad. (I also griped about the lack of a FutureTask-like
implementation of CompletionStage, but that is really a nit; not a major
complaint.)

As far as inter-op with legacy APIs, a #toFuture() method would have been
much better for a few reasons:

   1. Future is an interface, so a *view* could be returned instead of
   having to create a new stateful object that must be kept in sync with the
   original.
   2. Future provides inter-op but doesn't leak complete*/obtrude* methods
   (the heart of my objections)
   3. It could have been trivially implemented as a default method that
   just returns a CompletableFuture that is set from a #whenComplete stage,
   just as you've described.

(I'm pretty certain we agree on #1. At least most of it.)


Concern #2: Exposing non-blocking mechanisms to consume/use a future value
vs. blocking mechanisms. Mixing these is a very different beast from the
above. It isn't poor encapsulation/abstraction as neither has anything to
do with how the value is actually produced. Instead, one is a simple
derivative of the other (e.g. given a non-blocking mechanism, a blocking
mechanism can always be implemented on top of it). Providing both
facilitates simple, synchronous usage where it's appropriate (without
boiler-plate) and asynchronous non-blocking usage where it isn't.

You've already clearly expressed the opinion that blocking code is never
appropriate. I think that's a reasonable assertion for many contexts, just
not the JRE. Avoiding blocking altogether in core Java APIs is neither
realistic nor (again, IMO) desirable.

BTW, a #toFuture method, as I described above, would have been fine with me
as a means of achieving the properties I want in the API. It allows
blocking consumption without boiler-plate (stage.toFuture().get()) and it
does not leak implementation details of the Future to clients.


(Long-winded tangent ahead. Apologies if it sounds like a lecture.)

There is a spectrum. On one end (let's call it "simple"), you want a
programming model that makes it easier to write correct code and that is
easy to read, write, understand, and troubleshoot (at the extreme: all
synchronous, all blocking flows -- very simple to understand but will often
have poor performance and is incapable of taking advantage of today's
multi-core computers). On the other end ("performance"), you want a
programming model that enables taking maximum advantage of hardware,
provides greater efficiency, and facilitates better performance (greater
throughput, lower latency).

If we're being pragmatic, economics is the real decider for where on the
spectrum will strike the right balance. On the simple side, there's an
advantage to spending less in engineer-hours: most developers are more
productive writing simple synchronous code, and that style of code is much
easier to debug. But this can incur greater capital costs since it may
require more hardware to do the same job. On the performance side, it's the
converse: get more out of the money spent on compute resources, but
potentially spend more in engineering effort. (There are obviously other
constraints, too, like whether a particular piece of software has any value
at all if it can't meet certain performance requirements.)

My experience is that most organizations find their maxima close to the
middle, but nearer to the simple side. So there is an economic advantage
for them to focus a little more on developer productivity than on software
efficiency and performance.

I want my users to be as productive as possible, even if it means they
write blocking code. (And, let's face it, some of them will commit
atrocities far worse than just using a blocking API.)


(A bit of an aside: I know it's from 2008, but still relevant:
http://www.mailinator.com/tymaPaulMultithreaded.pdf)



----
*Josh Humphries*
Manager, Shared Systems  |  Platform Engineering
Atlanta, GA  |  678-400-4867
*Square* (www.squareup.com)

On Thu, Dec 4, 2014 at 1:13 PM, √iktor Ҡlang <viktor.klang at gmail.com> wrote:

>
>
> On Thu, Dec 4, 2014 at 7:11 PM, Josh Humphries <jh at squareup.com> wrote:
>
>>
>>> And I think Josh's point that blocking (invoking get()) is *orthogonal*
>>>> to
>>>> the ability for a reader/consumer to *write* the value of a computation,
>>>>
>>>
>>> Now that I think we can all agree on. But that was not how he phrased it
>>> AFAICT.
>>>
>>
>> Admittedly, I didn't use exactly that phrase. But that is precisely what
>> I meant when I wrote this:
>>
>> "So to me, splitting imperative completion and task-based implicit
>> completion into different interfaces is a different concern than splitting
>> blocking and non-blocking forms of consumption."
>>
>
> Thanks for clarifying. What I commented on was that mixing concerns seemed
> appropriate in one case, and discouraged in the other, without any
> rationale as to why that was OK for one thing but not the other. (I'm still
> very much interested in this)
>
>
>
>
> --
> Cheers,
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://cs.oswego.edu/pipermail/concurrency-interest/attachments/20141204/52fe7aae/attachment-0001.html>


More information about the Concurrency-interest mailing list