[concurrency-interest] CompletableFuture in Java 8

√iktor Ҡlang viktor.klang at gmail.com
Mon Dec 1 04:59:56 EST 2014


Hi Josh,

apologies for the delayed response.

On Fri, Nov 28, 2014 at 7:50 PM, Josh Humphries <jh at squareup.com> wrote:

> Thanks for your input, Viktor. It looks like our use cases are very
> different, hence our differences of opinion.
>
> We have plenty of services that use blocking I/O, JDBC, etc.
>

We avoid blocking at all cost, since the cost of pushing around contexts
and the thread wakeup lag on modern CPUs does not scale.


> We have no problem scaling over 4 cores (perhaps I don't understand what
> you mean by that expression, but we can easily saturate much greater than 4
> cores/host across multiple hosts). Because so many tools we use involve
> blocking, we use normal thread pools (ThreadPoolExecutorService), not
> ForkJoinPools.
>

Saturate cores is easy, having them do meaning ful work with all that is
hard (especially if you have any kind of blocking: see Amdahl's Law and
Neil Gunther's Universal Scalability Law)


>
> Since synchronous code is often easier to write and maintain and has not
> posed an issue for scalability for us,
>

The first part of the sentence does not really connect to the second one.
Synchronous code is often time easier to write initially and then much
harder to maintain because when one has scalability-issues and is not
familiar with how to swrite non-blocking asynchronous code, it is hard to
overcome performance/scalability issues imposed by the synchronous code.
At least this is my experience.


> the blocking option is fine for our purposes. But we tend to use
> asynchronous idioms under-the-hood in frameworks/libraries, to maximize
> parallelism and reduce latency.
>

In my experience it is rather fruitless to keep the innards async and
non-blocking if the first thing that end-users are going to do is to create
their own deadlocks, sprinkle timeouts everwhere or even worse, do
unbounded blocking.


>
> So, as a client of an asynchronous task, being able to do both blocking
> and non-blocking consumption is valuable as it means we don't need
> different APIs (or verbose wrappers/adapters from async->blocking) for
> typical usages, but it also still allows "power users" to easily do things
> asynchronously.
>

I strongly disagree. If it is -easier or as-easy- to do the wrong thing,
users will, without even thinking about it—do it.
I tried very hard to eliminate -all- blocking operations except for one,
external, way of doing it, and I don't know how much time has been wasted
helping users out who have blocked themselves into a corner.


>
> However, as the implementor of an asynchronous task, I don't like the fact
> that CompletableFuture gives the consumer a chance to actually produce the
> value (via the complete* and obtrude*).
>

This I -completely- agree with, which is why you'd never return a
CompletableFuture unless you wanted the user to cancel it, obtrude it or
otherwise modify it—instead you'd return a CompletionStage.


> CompletableFuture is an implementation detail of the *producer* -- it's
> one approach to fulfilling the result.
>

Absolutely! :)


> So having that in the public API that *clients* use (e.g. CompletionStage)
> is leaky.
>

Agreed. I'd prefer to have a static method on CompletableFuture that
optionally "wraps" the CompletionStage into a CompletableFuture or, if it
indeed is a CompletableFuture, casts and returns it.


>
> 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.
>

I don't see how you reach this conclusion based on the previous arguments,
could you elaborate in what way they are so different that it is not mixing
concerns in the latter case?


>
> BTW, I agree 100% about CompletionStage's API being way too broad. Keeping
> the API broad would probably be okay if they had default implementations,
> so that interface implementors need only worry about a handful of methods
> to actually implement instead of umpteen.
>

Ending the email on a happy note then! :-)


>
>
>
>
> ----
> *Josh Humphries*
> Manager, Shared Systems  |  Platform Engineering
> Atlanta, GA  |  678-400-4867
> *Square* (www.squareup.com)
>
> On Fri, Nov 28, 2014 at 11:57 AM, √iktor Ҡlang <viktor.klang at gmail.com>
> wrote:
>
>> Thanks for your reply, Josh!
>> Answers inline
>>
>> On Fri, Nov 28, 2014 at 3:29 PM, Josh Humphries <jh at squareup.com> wrote:
>>
>>> Below are some of my nits about the Java8 APIs:
>>>>>
>>>>> I disagree with how the CompletionStage interface is so tightly
>>>>> coupled to the CompletableFuture implementation. Why does CompletionStage
>>>>> have a toCompletableFuture() method? It feels like the interface should
>>>>> instead have just extended Future.
>>>>>
>>>>
>>>> I agree that `toCompletableFuture` is less than ideal, but having
>>>> CompletionStage extend Future would be way worse, there is not a single
>>>> method on j.u.c.Future that I'd ever want to call, or ever want any of my
>>>> users to call.
>>>>
>>>
>>> CompletionStage provides no way to actually get the result of
>>> computation,
>>>
>>
>> Most of the methods on it gives you the result. But I assume you mean
>> block to get the result.
>>
>>
>>> to block until it's done (or even query if it's done),
>>>
>>
>> I argue, based on experience, that blocking shouldn't be done in 99% of
>> the cases (blocking is the leading cause to why most software does not
>> scale beyond 4 threads), and if you really must do so, and know that it is
>> OK to block the current thread of execution, you can very easily:
>>
>> val cf = new CompletableFuture<R>()
>> stage.whenComplete((r, e) -> if (e != null) cf.completeExceptionally(e)
>> else cf.complete(r))
>> cf.get()
>>
>> or, in the current API: stage.toCompletableFuture().get()
>>
>>
>>> attempt to cancel the task,
>>>
>>
>> This is also something that should be avoided, in principle because it is
>> inherently racy, and also because if -all- consumers are allowed to cancel,
>> then you cannot safely share it among several consumers anymore because any
>> one of them may cancel it for everyone else, so this leads us into
>> defensive copying, which I think we all agree is an antipattern.
>>
>>
>>> etc. In my mind, those are the only reasons you'd ever call
>>> CompletionStage.toCompletableFuture, and I think having that behavior in
>>> the CompletionStage interface would have been better.
>>>
>>
>> I hope my arguments against it changes that conclusion :)
>>
>>
>>>
>>> I think a big benefit of ListenableFuture (and now CompletableFuture) is
>>> that you can use them in both blocking and fully asynchronous idioms. So
>>> being able to do things like get the result (or block for it) are valuable
>>> to me.
>>>
>>
>> In my experience, offing both blocking and non-blocking is the worst
>> option, because the ones that don't want to play nice will do blocking
>> everywhere, and then you're back to "won't scale beyond 4 cores". (I don't
>> know how much time I have wasted trying to convert blocking code to
>> non-blocking. And very, very rarely have I ever done the opposite.)
>>
>>
>>>
>>>
>>>
>>>> I dislike that the only CompletionStage implementation in the JRE,
>>>>> CompletableFuture, is like Guava's SettableFuture -- where the result is
>>>>> set imperatively. This has its uses for sure. But an implementation whose
>>>>> result comes from executing a unit of logic, like FutureTask
>>>>> (CompletionStageTask?), is a glaring omission.
>>>>>
>>>>
>>>> This is a one-liner tho: unitCompletableFuture.theApplyAsync( unit ->
>>>> createResult, executor)
>>>>
>>>
>>>
>>> Sorry if it wasn't clear. But my nit is about API purity, not
>>> functionality. Sure, the functionality is there (and
>>> CompletableFuture.supplyAsync is even more concise that your example). But
>>> the result is an object (CompletableFuture) that conflates the two styles
>>> of use for futures -- 1) completion being implicitly tied to the execution
>>> of a task and 2) imperative completion.
>>>
>>
>> I couldn't help but notice that you're objecting against having 2 styles
>> here, but argue that CompletionStage should support both blocking and
>> non-blocking operations. Is this intentional?
>>
>> In my experience, the more behavior an interface exposes (exhibit A:
>> java.util.Collection), the harder, and more costly, it will be for
>> implementors to implement it efficiently. I think CompletionStage offers a
>> pragmatic interface for multiple Future-style APIs to conform to and
>> implement that does not tie interface to implementation (like
>> CompletableFuture would do).
>>
>> Now, my preference would be to have removed the 3 versions of every
>> method (x, xAsync, xAsync + Executor) and instead only have `x` + Executor
>> and then you can supply a CallingThreadExecutor, ForkJoinPool.commonPool()
>> or <custom> and you get exactly the same behavior without the interface
>> pollution.
>>
>> Regarding CompletableFuture, I would probably personally not use it
>> because of `cancel`, `get`, `obtrude`, `complete` etc, and thanks to
>> CompletionStage I can avoid all that :)
>>
>> In scala.concurrent, we have Promise, which is the writer-handle
>> (write-once), and Future, which is the read-handle (read-many), we don't
>> support cancellation because it is not read-only. The only officially
>> supported way of blocking on the result of a Future is to go through the
>> auxiliary "Await.result(future, timeout)"-method (which makes it simple to
>> outlaw if one wants to avoid blocking). Noteworthy is that
>> "Await.result(…)" uses `scala.concurrent.blocking` demarcation so for
>> ForkJoinPools it can play nice with ManagedBlocker transparent to the user
>> code.
>>
>> So in the end, we encourage async transformation, clear separation
>> between readers and writer and if blocking is -mandated- it can play nice
>> with managed blocking. It has turned out very well, I'd say!
>>
>> If I have one regret, it is that I introduced "Await" at all. Blocking is
>> just bad.
>>
>>
>>>
>>>
>>>
>>>> On that note, an ExecutorService that returns CompletionStages (or lack
>>>>> thereof) is another obvious gap in the APIs in Java8 -- something like
>>>>> Guava's ListeningExecutorService. CompletableFuture's runAsync and
>>>>> supplyAsync methods do not suffice for a few reasons:
>>>>>
>>>>>    1. They don't allow interruption of the task when the future is
>>>>>    cancelled.
>>>>>    2. They can't throw checked exceptions. A callAsync method (taking
>>>>>    a Callable) would have been nice to avoid the try/catch boiler-plate in the
>>>>>    Supplier -- especially since CompletableFuture.completeExceptionally
>>>>>    readily supports checked exceptions.
>>>>>    3. They don't support scheduling the task for deferred execution
>>>>>    (like in a ScheduledExecutorService).
>>>>>
>>>>> Finally, I'm not fond of the presence of the obtrude* methods in
>>>>> CompletableFuture's public API. I feel like they should be protected (or
>>>>> maybe just not exist). Mutating a future's value, after it has completed
>>>>> and possibly already been inspected, seems like a potential source of
>>>>> hard-to-track-down bugs.
>>>>>
>>>>
>>>> TBH I think ExecutorService needs retirement, it mixes too many
>>>> concerns already. It would be extremely simple to introduce a new branch in
>>>> the Executor hierarchy and build on top of that instead.
>>>>
>>>>
>>>>>
>>>>>
>>>>> ----
>>>>> *Josh Humphries*
>>>>> Manager, Shared Systems  |  Platform Engineering
>>>>> Atlanta, GA  |  678-400-4867
>>>>> *Square* (www.squareup.com)
>>>>>
>>>>> On Thu, Nov 27, 2014 at 6:43 PM, Yu Lin <yu.lin.86 at gmail.com> wrote:
>>>>>
>>>>>> If you use ListenableFuture in Java8, it does support lambda
>>>>>> (they're independent). What I mean is ListenableFuture is pre-Java8,
>>>>>> so it's probably not designed to take advantage of lambda. To me,
>>>>>> async task chaining by Guava "Futures.transform" is harder to read than
>>>>>> CompletableFuture (with or without lambda).
>>>>>>
>>>>>> Josh, when you plan to jump to Java8, why do you use adapters from ListenableFuture
>>>>>> -> CompletionStage, why not change all ListenableFutures to CompletableFutures
>>>>>> (is it much harder than using adapters)? Adapter is just a wrapper
>>>>>> and makes the code more verbose.
>>>>>>
>>>>>> Also, how do you compare ListenableFuture and CompletableFuture? I'm
>>>>>> still trying to know the differences/advantages between them.
>>>>>>
>>>>>> On Thu, Nov 27, 2014 at 8:54 AM, Josh Humphries <jh at squareup.com>
>>>>>> wrote:
>>>>>>
>>>>>>> Why do you say ListenableFuture doesn't support lambdas? In Java8,
>>>>>>> any single-abstract-method interface can be the target of a lambda
>>>>>>> expressions, so Guava's Function is just as suited as the Function
>>>>>>> interface added in Java8.
>>>>>>>
>>>>>>> Perhaps you're only looking at the ListenableFuture interface --
>>>>>>> which just has addListener(Runnable,Executor). Take a look at Futures,
>>>>>>> which is full of static helper methods. They include additional methods for
>>>>>>> working with ListenableFutures and callbacks, including a way to
>>>>>>> "transform" a ListenableFuture with a Function. I'm pretty sure this was
>>>>>>> done so the ListenableFuture interface itself could be narrow without
>>>>>>> requiring implementations provide the full breadth (since it's pre-Java8,
>>>>>>> there is no luxury of default and static methods on the interface).
>>>>>>>
>>>>>>> At Square, we use ListenableFutures heavily in framework code (to
>>>>>>> support pre-Java8 apps). But we also have a lot of stuff moving to Java8.
>>>>>>> So I wrote adapters from ListenableFuture -> CompletionStage and vice
>>>>>>> versa. All of the building blocks needed are present in ListenableFuture.
>>>>>>> With these adapters, CompletableFutures (more importantly, the
>>>>>>> CompletionStage API) can still be used even though framework APIs expect
>>>>>>> and return ListenableFutures.
>>>>>>>
>>>>>>> But we actually have very little use of CompletableFutures. A lot of
>>>>>>> projects prefer serial code (with blocking operations) because the code is
>>>>>>> generally more readable (thus easier to maintain) and the performance is
>>>>>>> adequate. Code that is async and parallel is often encapsulated behind a
>>>>>>> library, which generally uses Java7 (and ListenableFutures) to support the
>>>>>>> various projects that haven't yet made the jump to Java8.
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> ----
>>>>>>> *Josh Humphries*
>>>>>>> Manager, Shared Systems  |  Platform Engineering
>>>>>>> Atlanta, GA  |  678-400-4867
>>>>>>> *Square* (www.squareup.com)
>>>>>>>
>>>>>>> On Thu, Nov 27, 2014 at 2:24 AM, Yu Lin <yu.lin.86 at gmail.com> wrote:
>>>>>>>
>>>>>>>> Yes, I'm looking at RxJava and Guava ListenableFuture. Do you guys
>>>>>>>> know what's the major differences among RxJava, ListenableFuture and
>>>>>>>> CompletableFuture. What's the advantage of each?
>>>>>>>>
>>>>>>>> It seems to me all three support callback listeners and async task
>>>>>>>> chaining. But ListenableFuture doesn't support lambda while RxJava support
>>>>>>>> more operations for Observable. How to choose which construct to use when
>>>>>>>> doing asynchronous programming?
>>>>>>>>
>>>>>>>> Thanks,
>>>>>>>> Yu
>>>>>>>>
>>>>>>>> On Thu, Nov 27, 2014 at 12:46 AM, Mohan Radhakrishnan <
>>>>>>>> radhakrishnan.mohan at gmail.com> wrote:
>>>>>>>>
>>>>>>>>> Don't you think Netflix should be using it ?
>>>>>>>>> http://netflix.github.io/#repo
>>>>>>>>>
>>>>>>>>> Thanks,
>>>>>>>>> Mohan
>>>>>>>>>
>>>>>>>>> On Thu, Nov 27, 2014 at 1:12 AM, Yu Lin <yu.lin.86 at gmail.com>
>>>>>>>>> wrote:
>>>>>>>>>
>>>>>>>>>> I guess David does mean RxJava. There're some projects use RxJava.
>>>>>>>>>> David, in which project do you use it? May I take a look if it's
>>>>>>>>>> open-source?
>>>>>>>>>>
>>>>>>>>>> On Fri, Nov 21, 2014 at 4:29 PM, Yu Lin <yu.lin.86 at gmail.com>
>>>>>>>>>> wrote:
>>>>>>>>>>
>>>>>>>>>>> OK, thanks for all your detailed analysis. I guess the slow
>>>>>>>>>>> adaption of a new version is why I can't find the use of it. I myself is
>>>>>>>>>>> using Java 6… Other similar libraries you mentioned seem to be interesting.
>>>>>>>>>>>
>>>>>>>>>>> On Fri, Nov 21, 2014 at 1:35 PM, Dávid Karnok <
>>>>>>>>>>> akarnokd at gmail.com> wrote:
>>>>>>>>>>>
>>>>>>>>>>>> Hi. CompletableFuture is great, but as a Ph.D R&D student
>>>>>>>>>>>> myself, I see a few shortcomings:- Slow adaptation of Java 8,- the feature
>>>>>>>>>>>> lag in a well known mobile platform and- the alternatives available as
>>>>>>>>>>>> library for some time.In addition, it may compose better than Future, but
>>>>>>>>>>>> IMHO, it doesn't go far enough. Therefore, I use a FOSS library (with
>>>>>>>>>>>> increasing popularity) that allows one to compose asynchronous data streams
>>>>>>>>>>>> with much less worry on concurrency and continuation than CF. I use it in
>>>>>>>>>>>> 50k loc projects, but I read/heard a well known company is switching almost
>>>>>>>>>>>> all of its software stack to the reactive/asynchronous programming idiom
>>>>>>>>>>>> this library enables. Excellent material is available (including videos and
>>>>>>>>>>>> conference talk) on this subject (and the library).
>>>>>>>>>>>> 2014.11.20. 2:45 ezt írta ("Yu Lin" <yu.lin.86 at gmail.com>):
>>>>>>>>>>>>
>>>>>>>>>>>>> Hi all,
>>>>>>>>>>>>>
>>>>>>>>>>>>> I'm a Ph.D. student at University of Illinois. I'm doing
>>>>>>>>>>>>> research on the uses of Java concurrent/asynchronous tasks. A new construct
>>>>>>>>>>>>> introduced in Java 8, CompletableFuture, seems to be a very nice and easy
>>>>>>>>>>>>> to use concurrent task.
>>>>>>>>>>>>>
>>>>>>>>>>>>> I'm looking for projects that use CompletableFuture. But since
>>>>>>>>>>>>> it's very new, I didn't find any real-world projects use it (only found
>>>>>>>>>>>>> many toy examples/demos).
>>>>>>>>>>>>>
>>>>>>>>>>>>> Does anyone know any projects that use it?
>>>>>>>>>>>>>
>>>>>>>>>>>>> Thanks,
>>>>>>>>>>>>> Yu Lin
>>>>>>>>>>>>>
>>>>>>>>>>>>> _______________________________________________
>>>>>>>>>>>>> Concurrency-interest mailing list
>>>>>>>>>>>>> Concurrency-interest at cs.oswego.edu
>>>>>>>>>>>>> http://cs.oswego.edu/mailman/listinfo/concurrency-interest
>>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> _______________________________________________
>>>>>>>>>> Concurrency-interest mailing list
>>>>>>>>>> Concurrency-interest at cs.oswego.edu
>>>>>>>>>> http://cs.oswego.edu/mailman/listinfo/concurrency-interest
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>
>>>>>>>> _______________________________________________
>>>>>>>> Concurrency-interest mailing list
>>>>>>>> Concurrency-interest at cs.oswego.edu
>>>>>>>> http://cs.oswego.edu/mailman/listinfo/concurrency-interest
>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>>
>>>>>
>>>>> _______________________________________________
>>>>> Concurrency-interest mailing list
>>>>> Concurrency-interest at cs.oswego.edu
>>>>> http://cs.oswego.edu/mailman/listinfo/concurrency-interest
>>>>>
>>>>>
>>>>
>>>>
>>>> --
>>>> Cheers,
>>>>>>>>
>>>
>>>
>>
>>
>> --
>> Cheers,
>>>>
>
>


-- 
Cheers,
√
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://cs.oswego.edu/pipermail/concurrency-interest/attachments/20141201/f32cf7ec/attachment-0001.html>


More information about the Concurrency-interest mailing list