Download presentation
Presentation is loading. Please wait.
Published byVerawati Inge Hadiman Modified over 6 years ago
1
Advanced Topics in Functional and Reactive Programming: Reactive Streams
Majeed Kassis
2
Agenda The Observer Pattern The Extended Observer Pattern
The Observable Contract Future, ListenableFuture , CompletableFuture Observable Streams: ReactiveX, RxJava Java Flow: Introduced in 2017
3
Why: The Observer Pattern
Problem: One change affects one or many objects. Many object behaviors depends on one object state. Objects are dynamically created and deleted Need broadcast communication Solution: Decouple classes into observers and subjects These objects can be coupled dynamically during runtime Communication: Pull mode - observers ask for information Push mode - subjects send their state to the observers
4
The Observer Pattern The Observer Pattern:
Defines one-to-many dependency between objects Once an object changes, its dependent objects are notified automatically This allows automatic update in case of state changes Instead of coupling classes at compile time Coupling of class instances is done at runtime as needed [loose-coupling] The observer pattern is a key part in the model–view–controller (MVC) architectural pattern Implements the View part of the model The observer pattern is implemented in almost all GUI toolkits! Example: Qt, Swing, etc…
5
The Observer Pattern Observer object: Observable object: [Subject]
An object that watches the state of another object It takes action as a response to state changes Observable object: [Subject] An object that allows observers to examine it It notifies its observers when its state changes Communication: push notifications
6
The Observer Pattern Benefits
Abstract coupling between subject and observer each can be extended and reused individually. Dynamic relationship between subject and observer It can be established at run time (can "hot-swap" views, etc) Allows programming flexibility. (loose-coupling) Broadcast communication: Notification is broadcast automatically to all interested objects that subscribed to it. Can be used to implement model-view separation in Java easily.
7
The Observer Pattern ConcreteObservers:
Classes that contain information specific to the current instance. The update function is called by the subject’s notify() operation. The observers update independently based on their current state. Observer The parent class of the concrete observers. It contains a subject instance. When an observer is initialized, it registers/attaches itself to the subject. Subject Class that has a list of observers. When its state changes it calls the notify()operation notifty() loops through all the observers by calling their update function Observer = Listener “Observable” – implements the ability to attach observers to it “Observer” – the class that handles an event in case it happens at the “observable” object
8
Model-View-Controller: GUI Applications
model: Classes in your system that are related to the internal representation of the state and behavior of the system. often part of the model is connected to file(s) or database(s) Implements the behavior of the system view: Classes in that display the state of the model to the user. generally, this is the GUI of the application (could also be a text UI) should not contain crucial application data Implements appearance of the system controller: classes that connect model and view defines how user interface reacts to user input (handles received events) receives messages from view (where events come from) sends messages to model (tells what data to display) Consists of the state of the system
9
Model-View-Controller: Flow
10
Issues in Observer Pattern
Unpredictable order of events The order in which events are received depends on the order of listeners registered. Missing first event problem How can we be sure that we registered our listener before receiving first event? Threading issues By default observer pattern is not thread safe! Leaking listeners Not deregistering listeners causes memory leaks. Accidental recursion The order of updating local state and notifying listeners is critical. Compounding complexity Using the pattern extensively will dramatically increase system complexity Leaking Listeners: Threading issues: Pitfalls:
11
The Observable Contract - Rules
Comes to solves the nightmare created by unrestricted use of the observer pattern The purpose is to ensures that it is easy to prove the correctness of operators and user code The Observable Contract defines: Observer and Observable Interface Communication between Observer/Observable Observable Termination Subscribing and Unsubscribing The use of multiple Observers Backpressure – what happens when consumer is slower than producer?
12
The Observable Contract: Observer Interface
An observer communicates with its Observable by implementing this interface Subscribe() Indicates that the observer is ready to receive notifications from the Observable Unsubscribe() Indicates that the observer no longer wants to receive notifications from the Observable Request() (optional) Indicates that the observer wants no more than a particular number of additional OnNext() notifications from the Observable
13
The Observable Contract: Observable Interface
The interface defines the functions used to facilitate communication between Observer and Observable OnNext() An Observable calls this method whenever the Observable emits an item This method takes as a parameter the item emitted by the Observable OnComplete() Indicates that the Observable has completed successfully and that it will be emitting no further items Once called, the subscription ends OnError() An Observable calls this method to indicate that it has failed to generate the expected data. It will not make further calls to onNext() or onCompleted() The onError() method takes as its parameter an indication of what caused the error OnSubscribe() (optional) Indicates that the Observable is ready to accept Request notifications from the observer
14
The Observable Contract: Observable Notifications
An Observable may make zero or more OnNext() notifications Each notification representing a single emitted item An observable may then follow those emission notifications by either an OnCompleted() or an OnError() notification, but not both. If one of these two notifications is sent, it will not issue any further notifications. Observables must issue notifications to observers serially there must be a formal happens-before relationship between the notifications It can be done in parallel as long as happens-before relationship is kept intact.
15
The Observable Contract: Observable Termination
If an Observable has not issued an OnCompleted() or OnError() notification, an observer may consider it to be still active Even if it is not currently emitting items And It may issue it notifications, such as an Unsubscribe or Request notification When an Observable does issue an OnCompleted() or OnError() notification, the Observable may release its resources and terminate Its observers should not attempt to communicate with it any further! An OnError() notification must contain the cause of the error It is invalid to call OnError() with a null value Before an Observable terminates it must first issue either an OnCompleted() or OnError() notification to all of the observers that are subscribed to it.
16
The Observable Contract: Subscribing and Unsubscribing
Once the Observable receives a Subscribe notification from the observer. An Observable may begin issuing notifications to an observer immediately Once an observer issues an Unsubscribe notification to an Observable The Observable will attempt to stop issuing notifications to the observer The observable might issue notifications, even after the unsubscribe command it sent, until the unsubscribe request is handled! When an Observable issues an OnError() or OnComplete() notification to its observers, this also ends the subscription No need to manually send an unsubscribe command.
17
The Observable Contract: Multiple Observers
If a second observer subscribes to an Observable that is already emitting items to a first observer The observable may emit the same items to each observer from its current index The observable may emit the complete sequence of items from the beginning to the second observer The observable may emit a wholly different sequence of items to the second observer There is no general guarantee that two observers of the same Observable will see the same sequence of items! It all depends on the implementation logic
18
The Observable Contract: Backpressure
The Problem: Fast Publisher, Slow Subscriber An Observable may implement backpressure if it detects that its observer implements Request notifications and understands OnSubscribe() notifications The Observable will not begin to emit items to the observer immediately upon subscription Instead, it will issue an OnSubscribe() notification to the observer! Then, observer may issue a Request notification to the Observable it has subscribed to, Requesting a number of items Requests are cumulative: Three Request notification to an Observable, for 3, 5 and 10, then 18 items will be returned at most
19
The Observable Contract: Backpressure
Since the Publisher is not allowed to signal more elements than the pending demand signaled by the Subscriber, it will have to abide to this back-pressure by applying one of the below strategies: not generate elements, if it is able to control their production rate, try buffering the elements in a bounded manner until more demand is signaled, drop elements until more demand is signaled tear down the stream if unable to apply any of the above strategies. This scenario effectively means that the Subscriber will pull the elements from the Publisher This mode of operation is referred to as pull-based back-pressure.
20
Extended Observer Pattern: Two New Additions and One Restriction
In case of no more data to be emitted: The producer signals the consumer that there is no more data available. An Observable calls its observer’s onCompleted() method. In case of exceptions at producer: The producer signals the consumer that an error has occurred An Observable calls its observer’s onError() method Restriction: Observer is always the Consumer Observable is always the Producer In ReactiveX – Observable Sequences represent the source of the data Data which was generated by some producer.
21
Observable Streams Iterable Streams: Observable Streams:
Traversing done by using an iterator The iterator pulls the data one by one from the stream Observable Streams: Data is emitted by the stream to requesting targets one by one Pushed as notifications! Element is Emitted when created or when received Targets must subscribe to the stream! All subscribing targets receive emitted element Implements the extended Observer Pattern!
22
Iterable vs Observable
23
Data Retrieval: Sync vs Async
Single Multiple Sync T getData() Iterable<T> getData() Async Future<T> getData() Observable<T> getData() Interaction with data can be done in 4 different ways.
24
Iterable<T> getData()
Single Multiple Sync T getData() Iterable<T> getData() Async Future<T> getData() Observable<T> getData() String s = getData(args); if (s.equals(x)) { // do something } else { // do something else } Typical synchronous scalar response with subsequent conditional logic. Data is fetched, and if-conditional is applied on the result to apply some logic.
25
Iterable<T> getData()
Single Multiple Sync T getData() Iterable<T> getData() Async Future<T> getData() Observable<T> getData() Iterable<String> values = getData(args); { for (String s : values) if (s.equals(x)) { // do something } else { // do something else } Similar to scalar value except conditional logic happens within a loop.
26
Iterable<T> getData()
Single Multiple Sync T getData() Iterable<T> getData() Async Future<T> getData() Observable<T> getData() Future<String> s = getData(args); if (s.get().equals(x)) { // do something } else { // do something else } As we move to async a normal Java Future is asynchronous but to apply conditional logic requires dereferencing the value via ‘get()’.
27
Iterable<T> getData()
Single Multiple Sync T getData() Iterable<T> getData() Async Future<T> getData() Observable<T> getData() Future<String> s = getData(args); if (s.get().equals(x)) { // do something } else { // do something else } And this leads to the typical issue in nested, conditional asynchronous code with Java Futures where asynchronous quickly becomes synchronous and blocking again.
28
ListenableFuture Object
Implemented in Google Guava: An open-source set of common libraries for Java, mainly developed by Google engineers. ListenableFuture allows to register callbacks Solves the problem with the blocking get() use in ordinary Java Future callbacks are executed once the computation is complete Callbacks handle two cases: onSuccess() – If the execution of the asynchronous call succeeds onFailure() – if the execution of the asynchronous call throws an exception
29
Iterable<T> getData()
Single Multiple Sync T getData() Iterable<T> getData() Async Future<T> getData() Observable<T> getData() ListenableFuture<String> s = getData(args); Futures.addCallback(s, new FutureCallback<String> { public void if onSuccess(String s) { (s.get().equals(x)) { // do something } else { // do something else public void if onFailure(Throwable t) { (s.equals(x)) { // handle error } });
30
CompletableFuture – Java 8
CompletableFuture class implements the Future interface and allows additional completion logic CompletableFuture supports synchronous callbacks: thenApply(Function<T,R>), allows applying a function on the result thenAccept(Consumer<T>), allows consuming the result thenRun(Runnable), allows executing a Runnable without using the result CompletableFuture supports asynchronous callbacks: thenApplyAsync(Function<T,R>) thenAcceptAsync(Consumer<T>) thenRunAsync(Runnable) Asynchronous callbacks are executed at the same thread Asynchronous callbacks are executed in a new thread
31
CompletableFuture: Examples 1
Can be manually completed! Allows asynchronous execution: [in ForkJoinPool.commonPool] CompletableFuture<String> completableFuture = new CompletableFuture<String>(); completableFuture.complete("Future's Result") CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new IllegalStateException(e); } System.out.println("I'll run in a separate thread than the main thread."); });
32
CompletableFuture: Examples 2
Allows asynchronous execution: [lambda implement Supplier] Allows to perform further action on the result without blocking: CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new IllegalStateException(e); } return "Result of the asynchronous computation"; }); CompletableFuture<String> welcomeText = CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new IllegalStateException(e); } return “Mark"; }).thenApply(name -> { return "Hello " + name; }).thenApply(greeting -> { return greeting + ", Welcome to ATD192!"; }); System.out.println(welcomeText.get());
33
CompletableFuture: Composition
Combine two dependent futures, the first is executed, and its result is sent to the next one as input: CompletableFuture<User> getUsersDetail(String userId) { return CompletableFuture.supplyAsync(() -> { UserService.getUserDetails(userId); }); } CompletableFuture<Double> getCreditRating(User user) { CreditRatingService.getCreditRating(user); Done by using thenCompose() : CompletableFuture<Double> result = getUserDetail(userId) .thenCompose(user -> getCreditRating(user));
34
CompletableFuture: Combination
thenCombine() is used when you want two Futures to run independently and do something after both are complete: System.out.println("Retrieving weight."); CompletableFuture<Double> weightInKgFuture = CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new IllegalStateException(e); } return 65.0; }); System.out.println("Retrieving height."); CompletableFuture<Double> heightInCmFuture = CompletableFuture.supplyAsync(() -> { return 177.8; System.out.println("Calculating BMI."); CompletableFuture<Double> combinedFuture = weightInKgFuture .thenCombine(heightInCmFuture, (weightInKg, heightInCm) -> { Double heightInMeter = heightInCm/100; return weightInKg/(heightInMeter*heightInMeter); System.out.println("Your BMI is - " + combinedFuture.get());
35
CompletableFuture: Handling Exceptions
Done by using exceptionally callback. Example: Integer age = -1; CompletableFuture<String> maturityFuture = CompletableFuture.supplyAsync(() -> { if(age < 0) { throw new IllegalArgumentException("Age can not be negative"); } if(age > 18) { return "Adult"; } else { return "Child"; } }).exceptionally(ex -> { System.out.println("Oops! We have an exception - " + ex.getMessage()); return "Unknown!"; }); System.out.println("Maturity : " + maturityFuture.get());
36
Iterable<T> getData()
Single Multiple Sync T getData() Iterable<T> getData() Async Future<T> getData() Observable<T> getData() CompletableFuture<String> s.thenApply((v) -> { if (v.equals(x)) { s = getData(args); // do something } else // { do something else } });
37
Iterable<T> getData()
Single Multiple Sync T getData() Iterable<T> getData() Async Future<T> getData() Observable<T> getData() CompletableFuture<String> s.thenApply((v) -> { if (v.equals(x)) { s = getData(args); // do something } else // { do something else } });
38
Iterable<T> getData()
Single Multiple Sync T getData() Iterable<T> getData() Async Future<T> getData() Observable<T> getData() Future<String> s = getData(args); s.map({ s -> if (s.equals(x)) { // do something } else { // do something else } }); Akka Futures are also composable and provide higher-order functions.
39
Iterable<T> getData()
Single Multiple Sync T getData() Iterable<T> getData() Async Future<T> getData() Observable<T> getData() Future<String> s.map({ s -> s = getData(args); if (s.equals(x)) { // do something } else { // do something else } }); That get us to where we want to be so that we can now compose conditional, nested data flows while remaining asynchronous.
40
Iterable<T> getData()
Single Multiple Sync T getData() Iterable<T> getData() Async Future<T> getData() Observable<T> getData() Observable<String> s = getData(args); s.map({ s -> if (s.equals(x)) { // do something } else { // do something else } }); Rx Observable supports multiple values which means it can handle a single value, a sequence of values or an infinite stream.
41
Iterable<T> getData()
Single Multiple Sync T getData() Iterable<T> getData() Async Future<T> getData() Observable<T> getData() Observable<String> s = getData(args); s.map({ s -> if (s.equals(x)) { // do something } else { // do something else } }); Rx Observable accommodates both, single and multi-valued responses Rx Observable facilitates higher-order functions to compose nested, conditional logic in a reactive manner.
42
Concluding: Iterable vs Observable
Iterable<T>, Iterator<t> Observable<T>, Observer<T> Get an iterator Subscribe an observer hasNext(), next() onNext(), onError(), onCompleted() Pull based Push based Sync async
43
Akka and Akka Streams Akka is a toolkit. Akka Streams Building Blocks
Implements the Actor model Used for high performance concurrency Akka also implements Reactive Streams Supports Back-pressured stream processing Akka Streams Building Blocks Source – the entry point to stream processing pipeline something with one output stream Flow – the processing building block something with one input and one output stream Sink – Flow is not executed until a terminal operation is registered something with one input stream, and no output RunnableGraph– ready to be run() stream Complete flow from source to sink
44
Akka Streams: Source A Source is a data creator: Creation Example:
It serves as an input source to the stream. Each Source has a single output channel and no input channel. All the data flows through the output channel to whatever is connected to the Source. Creation Example: NotUsed denotes unused variable, some might emit auxiliary information such as meta data final Source<Integer, NotUsed> source = Source.range(1, 100); final Source<Integer, NotUsed> source = Source.from(Arrays.asList(1, 2, 3, 4, 5));
45
Akka Streams: Flow Flows allow manipulating the data emitted by a Source They act as a connector between different streams They can be used to transform its elements Explicit Example: Attaching to Source: Source.from(Arrays.asList(1, 2, 3, 4)) .via(Flow.of(Integer.class).map(elem -> elem * 2)) .to(Sink.foreach(System.out::println)); final Source<Integer, NotUsed> source = Source.from(Arrays.asList(1, 2, 3, 4)) .map(elem -> elem * 2); source.to(Sink.foreach(System.out::println));
46
Akka Streams: Flow Attaching Sink to a flow: Operator Fusion:
async() allows exciting the operations in asynchronous manner Elements emitted from the first map are sent to another thread to be handled by the second map operation. final Sink<Integer, NotUsed> sink = Flow.of(Integer.class) .map(elem -> elem * 2).to(Sink.foreach(System.out::println)); Source.from(Arrays.asList(1, 2, 3, 4)).to(sink); Source.range(1, 3) .map(x -> x + 1).async() .map(x -> x * 2) .to(Sink.ignore());
47
Akka Streams: Sink A Sink is basically the opposite of a Source.
It is the endpoint of a stream and therefore consumes data. A Sink has a single input channel and no output channel. Sinks are especially needed when we want to specify the behavior of the data collector in a reusable way and without evaluating the stream. Sink Example: fold is a static function! Sink is the class. final Sink<Integer, CompletionStage<Integer>> sink = Sink.<Integer, Integer> fold(0, (aggr, next) -> aggr + next);
48
Akka Streams: Materializing the Stream
Materializer allows the execution of the complete flow Flow needs to have a source and destination attached RunnableGraph object is returned after attaching a terminal function RunnableGraph denotes that it is ready to be run() Code Examples: Executing the RunnableGraph: final RunnableGraph<CompletionStage<Integer>> runnable = Source.from( Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)).toMat(sink, Keep.right()); final CompletionStage<Integer> sum1 = runnable.run(mat);
49
ReactiveX – Observable Streams
Collection of utility functions that facilitate reactive programming. It contains Schedulers which makes threading simple. Operators to transform, combine, manipulate, and work with the sequence of items emitted by Observables In Essence: ReactiveX is a library for composing asynchronous and event-based programs by using observable sequences using functional programming. The stream of data is called Observables, which we receive over time. Instead of gathering them, we respond to each one of the elements in the stream. ReactiveX Is a Polyglot Implementation JavaScript, .Net, Objective-C, Java, etc. We will be talking about RxJava
50
RxObservable Streams In ReactiveX an observer subscribes to an Observable. Then that observer reacts to whatever item or sequence of items the Observable emits. This style allows concurrent operations since it does not block waiting for the Observable to emit its values. Instead, using the Observer pattern, the observer stands ready to react appropriately at whatever future time the Observable does so
51
subscribe(), blockingSubscribe()
Using subscribe function we register (connect) an Observer to listen to an Observable sequence emits. blockingSubscribe() makes the subscription a blocking one, by joining the current thread with the Observer. To solve the issue with limited Java8 Lambdas, subscribe is overloaded to fit all (most) possible combinations. This allows us to implement lambdas for each operation we require by implementing a Consumer/Action interfaces for the specific operation. onNext, onError, onSubscribe → Consumer interface. onComplete → Action interface.
52
Observable Types Three kinds of Observables: Hot, Cold, Connectable.
These observables answer the following question: When does an Observable begin emitting its sequence of items? Hot Observable: Begins emitting items as soon as it is created. Any observer who later subscribes to that Observable may start observing the sequence somewhere in the middle. Cold Observable: (default) Waits until an observer subscribes to it before it begins to emit items. Each observer is guaranteed to see the whole sequence from the beginning. Connectable Observable: Begins emitting values once its connect function is executed. Acts as Hot Observable after it begins emitting values.
53
Connectable Observable: using Publish
Using Publish, an ordinary observable is converted to a connectable observable. Publish: convert an ordinary Observable into a connectable Observable
54
General Flow in RxJava Creating an Observable.
Giving that Observable some data to emit. Creating an Observer. Assigning the Observer to an Observable. Giving the Observer tasks to perform whenever it receives an emission from its assigned Observable.
55
RxJava Reactive Types - io.reactivex.*
Flowable: Emits 0 or n items. Terminates with complete or an error. Supports backpressure. Observable: No backpressure support.
56
RxJava Reactive Types - io.reactivex.*
Single: The reactive version of Future. Emits either a single item or an error. onSubscribe(onSuccess | onError) Maybe: The reactive version of an Optional. Succeeds with an item, or no item, or emits an error. onSubscribe(onSuccess | onError | onComplete) Completable: The reactive version of a Runnable. Either completes or emits an error. It never return items, just like Runnable. onSubscribe(onComplete | onError)
57
Creating an Observable: Explicit Implementation
Observable<Integer> observable = Observable.create(new ObservableOnSubscribe<Integer>() { @Override public void subscribe(ObservableEmitter<Integer> e) throws Exception { //Use onNext to emit each item in the stream e.onNext(1); e.onNext(2); e.onNext(3); e.onNext(4); //Once the Observable has emitted all items in the sequence, call onComplete e.onComplete(); } );
58
Creating an Observable: Lambda Implementation
Observable<Integer> observable = Observable.create(e -> { //Use onNext to emit each item in the stream/ e.onNext(1); e.onNext(2); e.onNext(3); e.onNext(4); //Once the Observable has emitted all items in the sequence, call onComplete e.onComplete(); };
59
Another Observable Example
Observable<Todo> todoObservable = Observable.create(emitter -> { try { List<Todo> todos = getTodos(); // can be from a network, or some ThreadPool for (Todo todo : todos) { emitter.onNext(todo); } emitter.onComplete(); } catch (Exception e) { emitter.onError(e); });
60
Single Example Single<List<Todo>> todoSingle = Single.create(emitter -> { try { List<Todo> todos = getTodos(); emitter.onSuccess(todos); } } catch (Exception e) { emitter.onError(e); }); Single will generate a single item. onSuccess emits a single item, and unsubscribes Observers from the Observable.
61
Maybe Example: A05MaybeExample
Maybe<List<Todo>> todoMaybe = Maybe.create(emitter -> { try { List<Todo> todos = getTodos(); if(todos != null && !todos.isEmpty()) { emitter.onSuccess(todos); } else { emitter.onComplete(); } } catch (Exception e) { emitter.onError(e); }); Maybe will generate a single item, then apply a condition on the item. The Observable will emit onSuccess with a single item if the condition is met or emits onComplete if not. Finally it unsubscribes Observers from the Observable, in both cases.
62
Completable – A06 Example
Completable is mainly used when we wish to be notified once some job we wish to do is complete. We do not need it to return anything. In case of success we wish to be notified In case of failure we wish to be notified Completable must be done asynchronously otherwise it voids its purpose. Examples: Updating UI Updating database Saving values into files Sending files over network
63
Creating an Observer Observer<Integer> observer = new Observer<Integer>() { @Override public void onSubscribe(Disposable d) { System.out.println("onSubscribe: "); } public void onNext(Integer value) { System.out.println("onNext: " + value); public void onError(Throwable e) { System.out.println("onError: “ + e.getMessage()); public void onComplete() { System.out.println("onComplete: All Done!"); }; //Create our subscription observable.subscribe(observer); Disposable object is like ‘shouldStop’ object, which contains a Boolean, and two methods: dispose(), and isDisposable(). Once we execute observer.dispose(), we will sever the connection between the Observer and the Observable, which means that the Observer will no longer receive new items from the Observable.
64
More ways to create Observable
Observable.just() Converts any object into an Observable. The Observable will emit the object, and complete. Example: Observable<String> observable = Observable.just("Hello World!"); Observable.from() Converts any Collection of objects into an Observable stream. an array into an Observable using Observable.fromArray a Callable into an Observable using Observable.fromCallable an Iterable into an Observable using Observable.fromIterable
65
Even more ways to create Observables
Observable.range() Converts a range of sequential integers. The first integer rovided is the initial value, and the second is the number of integers to emit Example: Observable<Integer> observable = Observable.range(0, 5); Observable.interval() This operator creates an Observable that emits an infinite sequence of ascending integers, with each emission separated by a time interval. Example: Observable<Long> observable = Observable.interval(1, TimeUnit.SECONDS) Observable.empty() Creates an Observable that emits no items but terminates normally Mainly used for testing purposes. Example: Observable<String> observable = Observable.empty();
66
Using an Observable Observable.range(1, 10) .map(i -> i * 100)
.doOnNext(i -> printNumberWithThreadInfo("emitting", i)) .map(i -> i * 10) .subscribeOn(Schedulers.computation()) .observeOn(Schedulers.newThread()) .subscribe(integer -> printNumberWithThreadInfo("received", integer));
67
ThreadPool: Illustration
Tasks (Runnables) are created by user Tasks are submitted to the ThreadPool (dotted green) Tasks are queued in the TasksQueue Tasks are sent to Threads found in the ThreadPool CompletedTasks are returned to uesr
68
Schedulers By default, everything is done in the same thread!
an Observable and the chain of operators applied to it to do its work, will notify its observers, on the same thread on which its Subscribe method is called. observeOn operator specifies a different Scheduler that the Observable will use to send notifications to its observers. Its location affects the thread on which the Observable will be below its appearance. subscribeOn specifies a different Scheduler on which the Observable should operate.
69
Types of Schedulers Schedulers.computation( )
Uses own threadpool, and used for computational work (not I/O!) Schedulers.from(executor) Uses user specified threadpool. Schedulers.io( ) Used for i/o-bound tasks. Schedulers.newThread( ) Creates a new thread for the task to be ran on. Schedulers.trampoline( ) Queues work on current thread, after the completion of the current work.
70
Using Schedulers Without Subscribe, nothing happens. .subscribe()
All values emitted by Observables reach no Observers. .subscribe() Actions that do something and return void. All done on same thread. .subscribeOn() Which thread to subscribe on the Observable stream. .observeOn() Which thread runs the Observer
71
Example: All in Sync - A00 Example
For example, this Observable operation emits three String objects and maps their lengths: By default everything runs on the same thread! Which means not asynchronous behavior. Each item is generated, emitted, then consumed in a loop.
72
Without subscribeOn() – A01, A02 Examples
public static void main(String[] args) { Observable<String> source = Observable.just("Alpha","Beta","Gamma"); Observable<Integer> lengths = source.map(String::length); lengths.subscribe(l -> System.out.println("Received " + l + " on thread " + Thread.currentThread().getName())); } Output? Received 5 on thread main Received 4 on thread main Flow: The main thread executed the emissions of this Observable It pushed each emission through the map() operator to the Subscriber. Since the main thread becomes occupied with pushing the emissions, the program will not exit until the Observable is done pushing emissions and calls onCompleted().
73
Using subscribeOn() – A04 Example
public static void main(String[] args) { Observable<String> source = Observable.just("Alpha","Beta","Gamma"); Observable<Integer> lengths = source .subscribeOn(Schedulers.computation()) .map(String::length); lengths.blockingSubscribe(l -> System.out.println("Received " + l + " on thread " + Thread.currentThread().getName())); } Output? Received 5 on thread RxComputationThreadPool-1 Received 4 on thread RxComputationThreadPool-1
74
Using observeOn() – A03 Example
public static void main(String[] args) { Observable<Integer> source = Observable.range(1,5); source.map(i -> i * 100) .doOnNext(i -> System.out.println("Emitting " + i + " on thread " + Thread.currentThread().getName())) .observeOn(Schedulers.computation()) .map(i -> i * 10) .subscribe(i -> System.out.println("Received " + i + " on thread " + Thread.currentThread().getName())); sleep(3000); } Emitting 100 on thread main Emitting 200 on thread main Emitting 300 on thread main Emitting 400 on thread main Emitting 500 on thread main Received 1000 on thread RxComputationThreadPool-3 Received 2000 on thread RxComputationThreadPool-3 Received 3000 on thread RxComputationThreadPool-3 Received 4000 on thread RxComputationThreadPool-3 Received 5000 on thread RxComputationThreadPool-3
75
Different Composable Functions in RxJava
Transform: map, flatMap, reduce, scan Filter: take, skip, sample, takeWhile, filter,… Combine: concat, merge, zip, combineLatest, multicast, publish, cache, refCount, .. Concurrency: observeOn, subscribeOn Error Handling: onErrorReturn, onErrorResume ...
76
publish(): Converting a cold to hot stream – A07 Example
By default RxJava Streams are cold streams. Each time an Observer begins observing the stream it will begin from the start. Making streams hot can be done by using publish() function. As a result, the stream will emit the current value regardless of the time the observer begins observing. See code example. One hot stream. First observer begins receiving values from the start Second observer joins in the middle and receives from the current index only
77
Java Reactive Streams: java.util.concurrent.Flow
Added in Java 9. Released in 2017 The Flow API is an inter operation specification not an end-user API! It was made to allow current reactive libraries to support the Flow API natively: To be done through their own bridge implementation Or new libraries to be implemented Used as a common base for all reactive libraries to use
78
java.util.concurrent.Flow Interface
Flow API consists of 4 basic interfaces that can be used Subscriber - The Subscriber subscribes to the Publisher for the callbacks. 1 public static interface Subscriber<T> { 2 // This is the initializer function 3 public void onSubscribe(Subscription subscription); 4 // consume current element and retrieve the next one 5 public void onNext(T item); 6 // catches exception that might occur 7 public void onError(Throwable throwable); 8 // gets called when there are no more elements 9 public void onComplete(); 10 } onSubscribe - Method invoked prior to invoking any other Subscriber methods for the given Subscription.
79
java.util.concurrent.Flow Interface
Publisher - The publisher publishes the stream of data items to the registered subscribers. Subscription - Link between publisher and subscriber. Subscribers receive items only when requested, and may cancel at any time The methods in this interface are intended to be invoked only by their Subscribers 1 public static interface Publisher<T> { 2 public void subscribe(Subscriber<? super T> subscriber); 3 } 1 public static interface Subscription { 2 public void request(long n); 3 public void cancel(); 4 }
80
java.util.concurrent.Flow Interface
Processor - A component that acts as both a Subscriber and Publisher It extends Subscriber and Publisher! Using Flow can be done by implementing one of three options: Subscriber and Publisher Subscription, Subscriber, and Publisher Processor 1 public static interface Processor<T,R> extends Subscriber<T>, Publisher<R> { 2 }
81
Reactive Streams Flow in Java
Publisher emits data Subscriber subscribes to publisher Uses emitted data onSubscribe Publisher pushes data to Subscriber onNext onError onComplete java.util.concurrent.Flow
82
Flow Implementation Example
Let's create a basic subscriber which: asks for one data object prints it and asks for one more! Our Subscriber Logic: onSubscribe() – request first element onNext() – print element, then will request next element onError() – throw the error again onComplete() – print complete message
83
Subscriber Code: 1 1 package com.java.flow.api; 2
import java.util.concurrent.Flow; 3 public class MySubscriber<T> implements Flow.Subscriber<T> { 4 private Flow.Subscription subscription; 5 6 @Override 7 public void onSubscribe(Flow.Subscription subscription) { 8 this.subscription = subscription; 9 this.subscription.request(1); // Ask for initial one data object. 10 } 11 12 13 public void onNext(T item) { 14 System.out.println(item); // Print it. 15 subscription.request(1); // Ask for one more. 16
84
Subscriber Code: 2 17 @Override 18
public void onError(Throwable throwable) { 19 throwable.printStackTrace(); 20 } 21 22 23 public void onComplete() { 24 System.out.println("DONE"); // Done with the stream of data. 25 26
85
Execution Code: 1 1 package com.java.flow.api; 2 3
import java.util.List; 4 import java.util.concurrent.SubmissionPublisher; 5 6 public class Main { 7 8 public static void main(String[] args) { 9 List<String> items = List.of("1", "2", "3", "4", "5", "6", "7", "8", "9"); 10 SubmissionPublisher<String> publisher = new SubmissionPublisher<>(); 11 publisher.subscribe(new MySubscriber<>()); 12
86
Execution Code: 2 13 items.forEach(s -> { 14 try { 15
Thread.sleep(1000); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 publisher.submit(s); 20 }); 21 22 publisher.close(); 23 24
87
Execution Result Here you can easily see how the submit(T item) function of publisher invokes onNext(T item) function of subscriber. But it wouldn't have been possible without request(long n) function of the subscription. The delay of 1 sec helps us see the reactive magic happening.
Similar presentations
© 2025 SlidePlayer.com Inc.
All rights reserved.