[concurrency-interest] CompletableFuture in Java 8

√iktor Ҡlang viktor.klang at gmail.com
Wed Dec 17 04:44:19 EST 2014


On Mon, Dec 15, 2014 at 4:42 PM, Doug Lea <dl at cs.oswego.edu> wrote:

> On 12/13/2014 03:05 PM, √iktor Ҡlang wrote:
>
>> On Sat, Dec 13, 2014 at 4:17 PM, Doug Lea <dl at cs.oswego.edu
>> <mailto:dl at cs.oswego.edu>> wrote: It would be nice to simplify mixed
>> usages
>> across the various styles discussed on this and related threads, but this
>> one
>> is relatively straightforward even now:
>>
>> CompletableFuture.supplyAsync(__() -> data.parallelStream().reduce(.
>> __..)).
>> thenApply(...);
>>
>> Isn't the only reason why this works that they are both running on the
>> commonPool?
>>
>
> Well, it always "works" even if you use the non-common-pool
> parallel stream idiom, or use thenApplyAsync (vs thenApply)
> with some different Executor. Some of the combinations may
> transiently create and/or block some threads for the sake of
> join-dependencies and hand-offs.


It definitely depends on the ThreadFactory, doesn't it? (from a liveness
perspective)


> It is worth some exploration
> to streamline, but these are the kinds of cases in which
> occasional needs to add threads, blocking, or queuing are not
> usually major concerns, so long as there are no positive-feedback
> effects leading to unbounded growth.
>

Even growth alone can become a problem, especially for high-concurrency
applications (Akka for instance supports -millions- of concurrent
entities—if they all block…)


>
> The more challenging cases lie elsewhere. Here's some pretentious
> pontification about the underlying classic framework design issues.
>
> Concurrent, parallel, and/or distributed APIs tend to be
> either "pull" or "push" oriented:
>
> Pull: Consumers call functions (possibly calling others, possibly in
> parallel) returning results that are somehow used to produce visible
> effects.  These APIs tend to apply best when data sources for a
> computation already exist. Fork/Join-style frameworks generalize the
> idea of a function call to enable internal parallelism.
>
> Push: Producers trigger sets of computations (possibly in turn
> triggering others) when data become available. ultimately leading to
> some visible effects.  Reactive completion-based frameworks generalize
> the idea of interrupt-handlers etc to allow arbitrary async flows.
>
> In Java, Scala, etc, fluent lambda-based APIs have been found to be
> pretty good in helping to structure either kind of parallel
> computation, allowing (we hope) more productive exploitation of
> multicores with fewer errors than seen with ad-hoc uses of
> threads. But mixtures across these can run into performance and
> resource problems that we'd like to better address.
>
> Pull-style frameworks can encounter unbounded stalls and resources
> when function calls entail external communication via blocking IO. And
> Push-style frameworks cannot readily take advantage of efficient
> in-place parallel-dag scheduling and execution. (For example, you
> wouldn't want to use CompletableFutures for most divide-and-conquer
> parallel processing.)
>
> At the core library/JVM level though, concurrency support for both is
> roughly similar: Encapsulating as tasks held in various data
> structures and scheduling/managing execution. So in principle, we can
> support arbitrary combinations. We do some of this already.  The
> masochist-only CountedCompleter class requires manual continuation
> passing transforms of fork/join style processing that can co-exist
> with reactive processing. It is used internally in j.u.Stream, as well
> as mixed parallel/distributed frameworks including 0xdata H2O. But
> even ignoring the hostility/unusability factor, this approach alone
> does not provide a complete solution when used with user-supplied
> lambdas that can arbitrarily misbehave.
>
> Short of any grand unified scheme, one approach is to improve
> support that encourages replacements of the common enemy of
> both of these API styles -- blocking IO. Where near-term
> "encouragement" might take several forms, including adaptors
> and utilities for CompletionStages, j.u.Streams, and other APIs.


And there exists a hybrid solution (not having to choose "push" OR
"pull"—with push having nasty side-effect with unbounded growth if
consumers can't keep up, or having to resort to load-shedding—leading to
potential data loss or increased latencies/cost due to retransmissions; and
pull oftentimes having issues with performance due to the increased
communication cost—ACKing and the lock-step behavior usually associated
with that) that we use in "Reactive Streams"—www.reactive-streams.org—which
at runtime dynamically switches between push and pull depending on if the
consumer can't keep up or the producer can't keep up, without having to
block or do any load-shedding.

It would be interesting to see how JDK support for this could look like—
java.io is blocking, and java.nio isn't really user friendly.


>
> -Doug
>
>
>


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


More information about the Concurrency-interest mailing list