CPL 2016, week 6 Asynchronous execution Oleg Batrashev Institute - - PowerPoint PPT Presentation
CPL 2016, week 6 Asynchronous execution Oleg Batrashev Institute - - PowerPoint PPT Presentation
CPL 2016, week 6 Asynchronous execution Oleg Batrashev Institute of Computer Science, Tartu, Estonia March 14, 2016 Overview Studied so far: 1. Inter-thread visibility : JMM 2. Inter-thread synchronization : locks and monitors 3. Thread
Overview
Studied so far:
- 1. Inter-thread visibility: JMM
- 2. Inter-thread synchronization: locks and monitors
- 3. Thread management: executors, tasks, cancelation
- 4. Inter-thread communication: confinements, queues, back
pressure
- 5. Inter-thread collaboration: actors, inboxes, state diagrams
Today:
◮ Asynchronous execution: callbacks, Pyramid of Doom, Java
8 promises. Next weeks:
◮ Performance considerations: asynchronous IO, Java 8
streams.
Asynchronous execution 118/145 Why asynchronous -
Outline
Asynchronous execution Why asynchronous Callback hell Lambdas in Java 8 Promises in Java 8 Promises within actors Promise libraries
Asynchronous execution 119/145 Why asynchronous -
Synchronous vs asynchronous
Synchronous execution – algorithm executes sequentially, one step after another in single thread
res1 = doSomething1 () res2 = doSomething2 (res1) res3 = doSomething3 (res1) print (res2+res3) ◮ doSomething3 executes after doSomething2
◮ even with optimizations, think of the execution this way
Asynchronous execution – parts of the algorithm run separately, possibly in different threads
◮ execute doSomething2 and doSomething3 in separate threads
and wait for their results
◮ solvable with Java Futures
◮ execute them in separate threads but release this thread until
res2 and res3 are available, then execute printing
◮ not solvable with Java Futures
Asynchronous execution 120/145 Why asynchronous -
Problems of synchronous execution
◮ parallelism in the algorithm is not utilized (solved with Java
futures)
◮ current thread is not released even if nothing to do, consider res1 = doRequest1 () res2 = doRequest2 (res1) print (res2)
◮ if the thread is waiting for the result of request 1, it has
nothing else to do
◮ the thread is not released, it may not be used elsewhere ◮ e.g. making 10000 network requests or waiting for data from
10000 web clients requires the same number of threads
◮ solution: release the thread between doRequest1() and
doRequest2()
◮ how to do that?
Asynchronous execution 121/145 Why asynchronous -
Authentication example: the algorithm
Need for an algorithm with several steps that run over a network. Authentication with the salt and hash digest:
- 1. Client provides his name
- 2. Server checks if the name is ok and returns the salt
◮ the salt is just any random string
- 3. Client combines the salt and his password into single string, it
then computes the hash for it. The hash is sent to the server.
◮ hash is a sequence of bytes
- 4. Server compares received hash with the locally computed
- hash. If they are equal, then the password was correct. The
server sends resulting response to the client.
◮ password is in the server database
- 5. Client knows whether the authentication was successful
Advantage: the password is never sent over the network.
Asynchronous execution 122/145 Why asynchronous -
Authentication example: synchronous code
// send user name
- conn. sendMessage (new
- AuthComm. LoginRequest ("Oleg"));
// receive params or result response Message <Type > m1 = conn. readMessage (); if (m1.type == Type.RES_RESULT) { ResultResponse m = ( ResultResponse ) m1; System.out.println("Success "+m); return; } { // send password digest ParamsResponse m = ( ParamsResponse ) m1; MessageDigest digestAlgo = MessageDigest . getInstance ("SHA -1"); String password = " mypassword "; byte [] digest = digestAlgo .digest ((m.salt+password ). getBytes ())
- conn. sendMessage (new
- AuthComm. AuthRequest (digest ));
} // receive result response Message <Type > m2 = conn. readMessage (); { ResultResponse m = ( ResultResponse ) m2; System.out.println("Success "+m); return; }
Asynchronous execution 123/145 Callback hell -
Outline
Asynchronous execution Why asynchronous Callback hell Lambdas in Java 8 Promises in Java 8 Promises within actors Promise libraries
Asynchronous execution 124/145 Callback hell -
Callback solution
◮ give the method another one which should be executed when
the operations completes
doRequest1 (callback1 ); void callback1(res1) { doRequest2 (res1 , callback2 ); } void callback2(res2) { print (res2 ); } ◮ put into inline callbacks results in Pyramid of Doom (not Java
code!)
doRequest1 (void callback1(res1) { doRequest2 (res1 , void callback2(res2) { print (res2 ); }); });
Asynchronous execution 125/145 Callback hell -
Authentication example: callbacks
◮ response with the salt to the login request public interface ParamsCallback { void
- nSalt(String
salt ); } public void login(String username , ParamsCallback paramsCallback , ResultCallback resultCallback ) ◮ response with result to the auth request public interface ResultCallback { void
- nSuccess ();
void
- nFailure(String
error ); } public void authenticate (byte [] digest , ResultCallback callback) ◮ login may result in failure through the result callback: if the
username is invalid
Asynchronous execution 126/145 Callback hell -
Authentication example: Pyramid of Doom
◮ Java callbacks are 2 level, because they are in interfaces // send user name authProxy.login("Oleg", new ParamsCallback () { public void
- nSalt(String
salt) { // send password digest String password = " mypassword "; byte [] digest = algo.digest (( salt+password ). getBytes ());
- authProxy. authenticate (digest , new
ResultCallback () { public void
- nSuccess () {
System.out.println("Login was successful"); } public void
- nFailure(String
error) { System.out.println("Login failed: "+error ); } }); } }, null ); ◮ not pleasant to read, not reusable ◮ last null is the callback where error handling for login should
happen, omitted here
Asynchronous execution 127/145 Callback hell -
Named callbacks
◮ explicit classes: reusable code, but more difficult to follow public void run () { // send user name authProxy.login("Oleg", new ParamsCallbackImpl (), null ); } class ParamsCallbackImpl implements ParamsCallback { public void
- nSalt(String
salt) { // send password digest String password = " mypassword "; byte [] digest = algo.digest (( salt + password ). getBytes ());
- authProxy. authenticate (digest , new
ResultCallbackImpl ()); } } class ResultCallbackImpl implements ResultCallback { public void
- nSuccess () {
System.out.println("Login was successful"); } public void
- nFailure(String
error) { System.out.println("Login failed: " + error ); } } ◮ imagine if callbacks are split between different files
Asynchronous execution 128/145 Callback hell -
Callback summary
◮ asynchronous execution requires code that will be executed
later – callbacks
◮ inline callbacks result in the Pyramid of Doom, which is
unpleasant to read
◮ named callbacks split sequential logic into separate methods,
which is even more difficult to follow
◮ Java callback as an object with method – adds 2 levels to the
Pyramid of Doom Java 8 solutions:
◮ Java 8 promises (CompletableFuture) solve the problem with
unreadable and hard to follow code
◮ Java 8 lambdas solve the problem of 2-level callbacks
Asynchronous execution 129/145 Lambdas in Java 8 -
Outline
Asynchronous execution Why asynchronous Callback hell Lambdas in Java 8 Promises in Java 8 Promises within actors Promise libraries
Asynchronous execution 130/145 Lambdas in Java 8 -
Java 8 lambdas
◮ Syntactic sugar for “inline” methods: (arg1 ,arg2 ,arg3) -> { code ;..; return res; } ◮ It is possible to write: Runnable r = ()-> System.out.println("hello"); Callable <String > c1 = ()->"hello"; Callable <String > c2 = ()-> { System.out.println("in c2"); return "hello"; }; r.run (); c1.call (); c2.call (); ◮ However, the following is invalid, because need an interface: Object o = ()-> System.out.println("hello");
“The target type of this expression must be a functional interface”
◮ Java makes methods out of interface implementations ◮ functional interface[1, sec 1.3] must contain single method
declaration
Asynchronous execution 131/145 Lambdas in Java 8 -
Java 8 method references
Java 8 provides nice way to reference methods as functional interfaces:
◮ obj::methodName
◮ this::methodName
◮ ClassName::staticMethodName
java.util.function interfaces:
Function <Integer ,Integer > m1 = Math :: abs; BiFunction <Random ,Integer ,Integer > m2 = Random :: nextInt; BiConsumer <PrintStream ,char[]> m3 = PrintStream :: print;
Asynchronous execution 132/145 Lambdas in Java 8 -
Lambdas in Pyramid of Doom
// send user name authProxy.login("Oleg", salt
- > { // Java8
lambda // send password digest String password = " mypassword "; byte [] digest = algo.digest (( salt+password ). getBytes ());
- authProxy. authenticate (digest , new
ResultCallback () { public void
- nSuccess () {
System.out.println("Login was successful"); } public void
- nFailure(String
error) { System.out.println("Login failed: "+error ); } }); }, null ); ◮ problem with success/failure result, interface should contain
single method
◮ failure handling at each call may pollute the code
◮ Java 8 provides error forwarding for promises
Asynchronous execution 133/145 Promises in Java 8 -
Outline
Asynchronous execution Why asynchronous Callback hell Lambdas in Java 8 Promises in Java 8 Promises within actors Promise libraries
Asynchronous execution 134/145 Promises in Java 8 -
Problems of Java Future
◮ Java Future is the result of task execution
◮ it provides the result once execution is completed ◮ must submit a task before you can have its future ◮ what if want to return the future for the task executed third in
the sequence of async steps
◮ Java Future does not provide
fut.runThisCallback(callback) methods
◮ i.e. can only wait with fut.get() blocking the thread
Java 8 CompletableFuture (promise):
◮ may be completed at any time by promise.complete(res)
◮ only first result is preserved and valid
◮ have a bunch of promise.thenApply(callback) methods
◮ all registered callbacks are executed when promise is completed
Asynchronous execution 135/145 Promises in Java 8 -
Java 8 CompletableFuture
java.util.concurrent.CompletableFuture<Result>
◮ like Java Future completed once but no specific task required
◮ use complete(), completeExceptionally(), cancel()
◮ like java Future may be waited with get() or get(t,unit) ◮ unlike Java Futures “continuations” may be added to it CF <Void > thenRun(Runnable action) CF <Void > thenAccept (Consumer <? super T> action) CF <U> thenApply(Function <? super T,? extends U> fn) CF <U> thenCompose ( Function <? super T,? extends CompletionStage <U>> fn) CF <U> handle(BiFunction <? super T,Throwable ,? extends U> fn) CF <T> whenComplete ( BiConsumer <? super T,? super Throwable > action)
◮ the callbacks are executed after the promise is completed
◮ create the promise
◮ completedFuture(), runAsync(), supplyAsync()
Asynchronous execution 136/145 Promises in Java 8 -
Apply vs compose
After a promise has completed run a function with the result:
◮ apply callback returns (? extends U) ◮ compose callback returns (? extends CompletionStage<U>) ◮ both take value of type T from the source promise
Imagine we have CompletableFuture<String> is a username we get asynchronously (e.g. receive from network) Want to check the validity of a username:
- 1. Boolean checkLocally(String username) – only validates that
characters are ok, may be done synchronously
- 2. CF<Boolean> checkRemotely(String username) – validates
that username exists in a database, the result comes asynchronously Need to use apply in the first case and compose in the second:
- 1. unamePromise.thenApply(this::checkLocally)
- 2. unamePromise.thenCompose(this::checkRemotely)
Otherwise, compose returns CF<CF<Boolean>> – need to flatten
Asynchronous execution 137/145 Promises in Java 8 -
Handle vs whenComplete
◮ whenComplete() returns the old future
◮ i.e. it invokes the callback, but does not enhance the callback
chain
◮ handle() does return the new future
◮ this may be used to run actions when async action defined in
the handle method completes
Asynchronous execution 138/145 Promises in Java 8 -
Authentication example: promise chaining
◮ use thenCompose and handle to chain promises ◮ error in the chain is propagated to the last promise omitting all
subsequent callbacks in the chain
// send user name CompletableFuture <String > saltPromise = authProxy.login("Oleg"); CompletableFuture <Void > resultPromise = saltPromise . thenCompose (salt
- > {
// send password digest String password = " mypassword "; byte [] digest = algo.digest (( salt+password ). getBytes ()); return
- authProxy. authenticate (digest );
}); // handle all errors or final success resultPromise .handle ((v, err) -> { if (err == null) System.out.println("Login was successful"); else System.out.println("Login failed: "+err ); return null; });
Asynchronous execution 139/145 Promises in Java 8 -
Promise chaining with separate methods
◮ easier to see the overall process public void doAuth () throws IOException { authProxy.connect("localhost", 5000); CompletableFuture . completedFuture ("Oleg") . thenCompose (authProxy :: login) . thenCompose (this :: sendHashFromSaltAndPassword ) .handle(this :: printResult ); } CompletableFuture <Void > sendHashFromSaltAndPassword (String salt) { // send password digest String password = " mypassword "; byte [] digest = digestAlgo .digest (( salt+password ). getBytes ()); return
- authProxy. authenticate (digest );
} Void printResult (Void v, Throwable err) { if (err == null) System.out.println("Login was successful"); else System.out.println("Login failed: "+err ); return null; }
Asynchronous execution 140/145 Promises within actors -
Outline
Asynchronous execution Why asynchronous Callback hell Lambdas in Java 8 Promises in Java 8 Promises within actors Promise libraries
Asynchronous execution 141/145 Promises within actors -
Promise executors
Almost all methods provide 3 flavors, e.g. for run:
CF <Void > thenRun(Runnable action ); CF <Void > thenRunAsync (Runnable action ); CF <Void > thenRunAsync (Runnable action , Executor executor ); ◮ action may be executed in the thread that has completed the
promise
◮ action is executed by the ForkJoinPool.commonPool() ◮ action is executed by the provided executor
We must think about the consequences of synchronization or the lack of it!
Asynchronous execution 142/145 Promises within actors -
Promises within actors
◮ actor is inherently asynchronous
◮ its logic is split between message handling code pieces
◮ often it is more convenient to program sequence of actions,
like with authentication
◮ use promise chains inside an actor
Pay attention:
◮ callbacks must be executed in the actor thread, ◮ current state is not automatically checked when the callback is
executed,
◮ consider promise (chain) cancellation
◮ if done from the actor thread, the rest of the promise chain
won’t be executed
Asynchronous execution 143/145 Promise libraries -
Outline
Asynchronous execution Why asynchronous Callback hell Lambdas in Java 8 Promises in Java 8 Promises within actors Promise libraries
Asynchronous execution 144/145 Promise libraries -
Libraries
◮ Akka futures –
http://doc.akka.io/docs/akka/2.4.2/java/futures.html
◮ RxJava – https://github.com/ReactiveX/RxJava/wiki ◮ ReactiveX – http://reactivex.io/intro.html
Asynchronous execution 145/145 Promise libraries -
Summary
◮ asynchronous execution is necessary to utilize more resources
and release threads when not needed
◮ callbacks allow to “continue” execution, but it results in
◮ Pyramid of Doom or very scattered callback code
◮ Java futures help with resource utilization but not thread
release
◮ Java 8 completable future (promise) solves the problem
◮ allow to add callbacks through the then*() methods