From 73606dae1a45cea3ca6391137d79f4891daf88a0 Mon Sep 17 00:00:00 2001 From: Tapio Rautonen Date: Mon, 20 Jul 2015 22:55:39 +0300 Subject: [PATCH 1/7] #68: Implementation of Async Method Invocation pattern --- async-method-invocation/pom.xml | 18 ++++ .../iluwatar/async/method/invocation/App.java | 50 +++++++++ .../method/invocation/AsyncCallback.java | 9 ++ .../method/invocation/AsyncExecutor.java | 14 +++ .../async/method/invocation/AsyncResult.java | 12 +++ .../invocation/ThreadAsyncExecutor.java | 101 ++++++++++++++++++ .../async/method/invocation/AppTest.java | 13 +++ pom.xml | 1 + 8 files changed, 218 insertions(+) create mode 100644 async-method-invocation/pom.xml create mode 100644 async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java create mode 100644 async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java create mode 100644 async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java create mode 100644 async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java create mode 100644 async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java create mode 100644 async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java diff --git a/async-method-invocation/pom.xml b/async-method-invocation/pom.xml new file mode 100644 index 000000000..be932bca5 --- /dev/null +++ b/async-method-invocation/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.3.0 + + async-method-invocation + + + junit + junit + test + + + diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java new file mode 100644 index 000000000..f59f831b2 --- /dev/null +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java @@ -0,0 +1,50 @@ +package com.iluwatar.async.method.invocation; + +import java.util.concurrent.Callable; + +public class App { + + public static void main(String[] args) throws Exception { + AsyncExecutor executor = new ThreadAsyncExecutor(); + AsyncResult asyncResult1 = executor.startProcess(lazyval(10, 500)); + AsyncResult asyncResult2 = executor.startProcess(lazyval("test", 300)); + AsyncResult asyncResult3 = executor.startProcess(lazyval(50L, 700)); + AsyncResult asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4")); + AsyncResult asyncResult5 = executor.startProcess(lazyval("callback", 600), callback("Callback result 5")); + + Thread.sleep(350); // Oh boy I'm working hard here + log("Some hard work done"); + + Integer result1 = executor.endProcess(asyncResult1); + String result2 = executor.endProcess(asyncResult2); + Long result3 = executor.endProcess(asyncResult3); + asyncResult4.await(); + asyncResult5.await(); + + log("Result 1: " + result1); + log("Result 2: " + result2); + log("Result 3: " + result3); + } + + private static Callable lazyval(T value, long delayMillis) { + return () -> { + Thread.sleep(delayMillis); + log("Task completed with: " + value); + return value; + }; + } + + private static AsyncCallback callback(String name) { + return (value, ex) -> { + if (ex.isPresent()) { + log(name + " failed: " + ex.map(Exception::getMessage).orElse("")); + } else { + log(name + ": " + value); + } + }; + } + + private static void log(String msg) { + System.out.println(String.format("[%1$-10s] - %2$s", Thread.currentThread().getName(), msg)); + } +} diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java new file mode 100644 index 000000000..0477f70e1 --- /dev/null +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java @@ -0,0 +1,9 @@ +package com.iluwatar.async.method.invocation; + +import java.util.Optional; + +public interface AsyncCallback { + + void onComplete(T value, Optional ex); + +} diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java new file mode 100644 index 000000000..dd23a8bfb --- /dev/null +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java @@ -0,0 +1,14 @@ +package com.iluwatar.async.method.invocation; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; + +public interface AsyncExecutor { + + AsyncResult startProcess(Callable task); + + AsyncResult startProcess(Callable task, AsyncCallback callback); + + T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException; + +} diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java new file mode 100644 index 000000000..5bf6145b8 --- /dev/null +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java @@ -0,0 +1,12 @@ +package com.iluwatar.async.method.invocation; + +import java.util.concurrent.ExecutionException; + +public interface AsyncResult { + + boolean isCompleted(); + + T getValue() throws ExecutionException; + + void await() throws InterruptedException; +} diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java new file mode 100644 index 000000000..b368e284d --- /dev/null +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java @@ -0,0 +1,101 @@ +package com.iluwatar.async.method.invocation; + +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; + +public class ThreadAsyncExecutor implements AsyncExecutor { + + private final AtomicInteger idx = new AtomicInteger(0); + + @Override + public AsyncResult startProcess(Callable task) { + return startProcess(task, null); + } + + @Override + public AsyncResult startProcess(Callable task, AsyncCallback callback) { + CompletableResult result = new CompletableResult<>(callback); + new Thread(() -> { + try { + result.setValue(task.call()); + } catch (Exception ex) { + result.setException(ex); + } + }, "executor-" + idx.incrementAndGet()).start(); + return result; + } + + @Override + public T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException { + if (asyncResult.isCompleted()) { + return asyncResult.getValue(); + } else { + asyncResult.await(); + return asyncResult.getValue(); + } + } + + private static class CompletableResult implements AsyncResult { + + static final int RUNNING = 1; + static final int FAILED = 2; + static final int COMPLETED = 3; + + final Object lock; + final Optional> callback; + + volatile int state = RUNNING; + T value; + Exception exception; + + CompletableResult(AsyncCallback callback) { + this.lock = new Object(); + this.callback = Optional.ofNullable(callback); + } + + void setValue(T value) { + this.value = value; + this.state = COMPLETED; + this.callback.ifPresent(ac -> ac.onComplete(value, Optional.empty())); + synchronized (lock) { + lock.notifyAll(); + } + } + + void setException(Exception exception) { + this.exception = exception; + this.state = FAILED; + this.callback.ifPresent(ac -> ac.onComplete(null, Optional.of(exception))); + synchronized (lock) { + lock.notifyAll(); + } + } + + @Override + public boolean isCompleted() { + return (state > RUNNING); + } + + @Override + public T getValue() throws ExecutionException { + if (state == COMPLETED) { + return value; + } else if (state == FAILED) { + throw new ExecutionException(exception); + } else { + throw new IllegalStateException("Execution not completed yet"); + } + } + + @Override + public void await() throws InterruptedException { + synchronized (lock) { + if (!isCompleted()) { + lock.wait(); + } + } + } + } +} diff --git a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java new file mode 100644 index 000000000..989fa6d1d --- /dev/null +++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java @@ -0,0 +1,13 @@ +package com.iluwatar.async.method.invocation; + +import org.junit.Test; + +public class AppTest { + + @Test + public void test() throws Exception { + String[] args = {}; + App.main(args); + } + +} diff --git a/pom.xml b/pom.xml index 2cbc41b71..fe92fd404 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,7 @@ dependency-injection naked-objects front-controller + async-method-invocation From 064aa26d444f7b43999a9615dcb2453ec2451414 Mon Sep 17 00:00:00 2001 From: Tapio Rautonen Date: Tue, 21 Jul 2015 00:28:24 +0300 Subject: [PATCH 2/7] #68: Javadocs. --- .../iluwatar/async/method/invocation/App.java | 52 +++++++++++++++++++ .../method/invocation/AsyncCallback.java | 6 +++ .../method/invocation/AsyncExecutor.java | 23 ++++++++ .../async/method/invocation/AsyncResult.java | 17 ++++++ .../invocation/ThreadAsyncExecutor.java | 24 +++++++++ 5 files changed, 122 insertions(+) diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java index f59f831b2..de4dfe926 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java @@ -2,30 +2,76 @@ package com.iluwatar.async.method.invocation; import java.util.concurrent.Callable; +/** + *

+ * This application demonstrates the async method invocation pattern. Key parts of the pattern are + * AsyncResult which is an intermediate container for an asynchronously evaluated value, + * AsyncCallback which can be provided to be executed on task completion and + * AsyncExecutor that manages the execution of the async tasks. + *

+ *

+ * The main method shows example flow of async invocations. The main thread starts multiple tasks with + * variable durations and then continues its own work. When the main thread has done it's job it collects + * the results of the async tasks. Two of the tasks are handled with callbacks, meaning the callbacks are + * executed immediately when the tasks complete. + *

+ *

+ * Noteworthy difference of thread usage between the async results and callbacks is that the async results + * are collected in the main thread but the callbacks are executed within the worker threads. This should be + * noted when working with thread pools. + *

+ *

+ * Java provides its own implementations of async method invocation pattern. FutureTask, CompletableFuture + * and ExecutorService are the real world implementations of this pattern. But due to the nature of parallel + * programming, the implementations are not trivial. This example does not take all possible scenarios into + * account but rather provides a simple version that helps to understand the pattern. + *

+ * + * @see AsyncResult + * @see AsyncCallback + * @see AsyncExecutor + * + * @see java.util.concurrent.FutureTask + * @see java.util.concurrent.CompletableFuture + * @see java.util.concurrent.ExecutorService + */ public class App { public static void main(String[] args) throws Exception { + // construct a new executor that will run async tasks AsyncExecutor executor = new ThreadAsyncExecutor(); + + // start few async tasks with varying processing times, two last with callback handlers AsyncResult asyncResult1 = executor.startProcess(lazyval(10, 500)); AsyncResult asyncResult2 = executor.startProcess(lazyval("test", 300)); AsyncResult asyncResult3 = executor.startProcess(lazyval(50L, 700)); AsyncResult asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4")); AsyncResult asyncResult5 = executor.startProcess(lazyval("callback", 600), callback("Callback result 5")); + // emulate processing in the current thread while async tasks are running in their own threads Thread.sleep(350); // Oh boy I'm working hard here log("Some hard work done"); + // wait for completion of the tasks Integer result1 = executor.endProcess(asyncResult1); String result2 = executor.endProcess(asyncResult2); Long result3 = executor.endProcess(asyncResult3); asyncResult4.await(); asyncResult5.await(); + // log the results of the tasks, callbacks log immediately when complete log("Result 1: " + result1); log("Result 2: " + result2); log("Result 3: " + result3); } + /** + * Creates a callable that lazily evaluates to given value with artificial delay. + * + * @param value value to evaluate + * @param delayMillis artificial delay in milliseconds + * @return new callable for lazy evaluation + */ private static Callable lazyval(T value, long delayMillis) { return () -> { Thread.sleep(delayMillis); @@ -34,6 +80,12 @@ public class App { }; } + /** + * Creates a simple callback that logs the complete status of the async result. + * + * @param name callback name + * @return new async callback + */ private static AsyncCallback callback(String name) { return (value, ex) -> { if (ex.isPresent()) { diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java index 0477f70e1..067b79d43 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java @@ -4,6 +4,12 @@ import java.util.Optional; public interface AsyncCallback { + /** + * Complete handler which is executed when async task is completed or fails execution. + * + * @param value the evaluated value from async task, undefined when execution fails + * @param ex empty value if execution succeeds, some exception if executions fails + */ void onComplete(T value, Optional ex); } diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java index dd23a8bfb..4bf837a4f 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java @@ -5,10 +5,33 @@ import java.util.concurrent.ExecutionException; public interface AsyncExecutor { + /** + * Starts processing of an async task. Returns immediately with async result. + * + * @param task task to be executed asynchronously + * @return async result for the task + */ AsyncResult startProcess(Callable task); + /** + * Starts processing of an async task. Returns immediately with async result. Executes callback + * when the task is completed. + * + * @param task task to be executed asynchronously + * @param callback callback to be executed on task completion + * @return async result for the task + */ AsyncResult startProcess(Callable task, AsyncCallback callback); + /** + * Ends processing of an async task. Blocks the current thread if necessary and returns the + * evaluated value of the completed task. + * + * @param asyncResult async result of a task + * @return evaluated value of the completed task + * @throws ExecutionException if execution has failed, containing the root cause + * @throws InterruptedException if the execution is interrupted + */ T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException; } diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java index 5bf6145b8..689095e9d 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java @@ -4,9 +4,26 @@ import java.util.concurrent.ExecutionException; public interface AsyncResult { + /** + * Status of the async task execution. + * + * @return true if execution is completed or failed + */ boolean isCompleted(); + /** + * Gets the value of completed async task. + * + * @return evaluated value or throws ExecutionException if execution has failed + * @throws ExecutionException if execution has failed, containing the root cause + * @throws IllegalStateException if execution is not completed + */ T getValue() throws ExecutionException; + /** + * Blocks the current thread until the async task is completed. + * + * @throws InterruptedException if the execution is interrupted + */ void await() throws InterruptedException; } diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java index b368e284d..18b27c3b6 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java @@ -5,8 +5,12 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; +/** + * Implementation of async executor that creates a new thread for every task. + */ public class ThreadAsyncExecutor implements AsyncExecutor { + /** Index for thread naming */ private final AtomicInteger idx = new AtomicInteger(0); @Override @@ -37,6 +41,14 @@ public class ThreadAsyncExecutor implements AsyncExecutor { } } + /** + * Simple implementation of async result that allows completing it successfully with a value + * or exceptionally with an exception. A really simplified version from its real life cousins + * FutureTask and CompletableFuture. + * + * @see java.util.concurrent.FutureTask + * @see java.util.concurrent.CompletableFuture + */ private static class CompletableResult implements AsyncResult { static final int RUNNING = 1; @@ -55,6 +67,12 @@ public class ThreadAsyncExecutor implements AsyncExecutor { this.callback = Optional.ofNullable(callback); } + /** + * Sets the value from successful execution and executes callback if available. Notifies + * any thread waiting for completion. + * + * @param value value of the evaluated task + */ void setValue(T value) { this.value = value; this.state = COMPLETED; @@ -64,6 +82,12 @@ public class ThreadAsyncExecutor implements AsyncExecutor { } } + /** + * Sets the exception from failed execution and executes callback if available. Notifies + * any thread waiting for completion. + * + * @param exception exception of the failed task + */ void setException(Exception exception) { this.exception = exception; this.state = FAILED; From 2c52d11b432f74848f453a054fb1782caea6dd9d Mon Sep 17 00:00:00 2001 From: Tapio Rautonen Date: Wed, 22 Jul 2015 23:18:07 +0300 Subject: [PATCH 3/7] #68: Documented async method invocation. --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 000c707c9..5de7711ec 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ Concurrency patterns are those types of design patterns that deal with the multi * [Double Checked Locking](#double-checked-locking) * [Thread Pool](#thread-pool) +* [Async Method Invocation](#async-method-invocation) ### Presentation Tier Patterns @@ -618,6 +619,18 @@ validation and for building to order **Applicability:** Use the Thread Pool pattern when * You have a large number of short-lived tasks to be executed in parallel +## Async Method Invocation [↑](#list-of-design-patterns) +**Intent:** Asynchronous method invocation is pattern where the calling thread is not blocked while waiting results of tasks. The pattern provides parallel processing of multiple independent tasks and retrieving the results via callbacks or waiting until everything is done. + +**Applicability:** Use async method invocation pattern when +* You have multiple independent tasks that can run in parallel +* You need to improve performance of running a group of sequential tasks +* You have limited number of processing capacity or long running tasks and the caller cannot wait the tasks to be ready + +**Real world examples:** +* [FutureTask](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/FutureTask.html), [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) and [ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) (Java) +* [Task-based Asynchronous Pattern](https://msdn.microsoft.com/en-us/library/hh873175.aspx) (.NET) + ## Private Class Data [↑](#list-of-design-patterns) **Intent:** Private Class Data design pattern seeks to reduce exposure of attributes by limiting their visibility. It reduces the number of class attributes by encapsulating them in single Data object. From 3e8ef01288d862ea9e6db0a8f8aa0727aff12dfd Mon Sep 17 00:00:00 2001 From: Tapio Rautonen Date: Mon, 20 Jul 2015 22:55:39 +0300 Subject: [PATCH 4/7] #68: Implementation of Async Method Invocation pattern --- async-method-invocation/pom.xml | 18 ++++ .../iluwatar/async/method/invocation/App.java | 50 +++++++++ .../method/invocation/AsyncCallback.java | 9 ++ .../method/invocation/AsyncExecutor.java | 14 +++ .../async/method/invocation/AsyncResult.java | 12 +++ .../invocation/ThreadAsyncExecutor.java | 101 ++++++++++++++++++ .../async/method/invocation/AppTest.java | 13 +++ pom.xml | 1 + 8 files changed, 218 insertions(+) create mode 100644 async-method-invocation/pom.xml create mode 100644 async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java create mode 100644 async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java create mode 100644 async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java create mode 100644 async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java create mode 100644 async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java create mode 100644 async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java diff --git a/async-method-invocation/pom.xml b/async-method-invocation/pom.xml new file mode 100644 index 000000000..be932bca5 --- /dev/null +++ b/async-method-invocation/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.3.0 + + async-method-invocation + + + junit + junit + test + + + diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java new file mode 100644 index 000000000..f59f831b2 --- /dev/null +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java @@ -0,0 +1,50 @@ +package com.iluwatar.async.method.invocation; + +import java.util.concurrent.Callable; + +public class App { + + public static void main(String[] args) throws Exception { + AsyncExecutor executor = new ThreadAsyncExecutor(); + AsyncResult asyncResult1 = executor.startProcess(lazyval(10, 500)); + AsyncResult asyncResult2 = executor.startProcess(lazyval("test", 300)); + AsyncResult asyncResult3 = executor.startProcess(lazyval(50L, 700)); + AsyncResult asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4")); + AsyncResult asyncResult5 = executor.startProcess(lazyval("callback", 600), callback("Callback result 5")); + + Thread.sleep(350); // Oh boy I'm working hard here + log("Some hard work done"); + + Integer result1 = executor.endProcess(asyncResult1); + String result2 = executor.endProcess(asyncResult2); + Long result3 = executor.endProcess(asyncResult3); + asyncResult4.await(); + asyncResult5.await(); + + log("Result 1: " + result1); + log("Result 2: " + result2); + log("Result 3: " + result3); + } + + private static Callable lazyval(T value, long delayMillis) { + return () -> { + Thread.sleep(delayMillis); + log("Task completed with: " + value); + return value; + }; + } + + private static AsyncCallback callback(String name) { + return (value, ex) -> { + if (ex.isPresent()) { + log(name + " failed: " + ex.map(Exception::getMessage).orElse("")); + } else { + log(name + ": " + value); + } + }; + } + + private static void log(String msg) { + System.out.println(String.format("[%1$-10s] - %2$s", Thread.currentThread().getName(), msg)); + } +} diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java new file mode 100644 index 000000000..0477f70e1 --- /dev/null +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java @@ -0,0 +1,9 @@ +package com.iluwatar.async.method.invocation; + +import java.util.Optional; + +public interface AsyncCallback { + + void onComplete(T value, Optional ex); + +} diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java new file mode 100644 index 000000000..dd23a8bfb --- /dev/null +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java @@ -0,0 +1,14 @@ +package com.iluwatar.async.method.invocation; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; + +public interface AsyncExecutor { + + AsyncResult startProcess(Callable task); + + AsyncResult startProcess(Callable task, AsyncCallback callback); + + T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException; + +} diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java new file mode 100644 index 000000000..5bf6145b8 --- /dev/null +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java @@ -0,0 +1,12 @@ +package com.iluwatar.async.method.invocation; + +import java.util.concurrent.ExecutionException; + +public interface AsyncResult { + + boolean isCompleted(); + + T getValue() throws ExecutionException; + + void await() throws InterruptedException; +} diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java new file mode 100644 index 000000000..b368e284d --- /dev/null +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java @@ -0,0 +1,101 @@ +package com.iluwatar.async.method.invocation; + +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; + +public class ThreadAsyncExecutor implements AsyncExecutor { + + private final AtomicInteger idx = new AtomicInteger(0); + + @Override + public AsyncResult startProcess(Callable task) { + return startProcess(task, null); + } + + @Override + public AsyncResult startProcess(Callable task, AsyncCallback callback) { + CompletableResult result = new CompletableResult<>(callback); + new Thread(() -> { + try { + result.setValue(task.call()); + } catch (Exception ex) { + result.setException(ex); + } + }, "executor-" + idx.incrementAndGet()).start(); + return result; + } + + @Override + public T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException { + if (asyncResult.isCompleted()) { + return asyncResult.getValue(); + } else { + asyncResult.await(); + return asyncResult.getValue(); + } + } + + private static class CompletableResult implements AsyncResult { + + static final int RUNNING = 1; + static final int FAILED = 2; + static final int COMPLETED = 3; + + final Object lock; + final Optional> callback; + + volatile int state = RUNNING; + T value; + Exception exception; + + CompletableResult(AsyncCallback callback) { + this.lock = new Object(); + this.callback = Optional.ofNullable(callback); + } + + void setValue(T value) { + this.value = value; + this.state = COMPLETED; + this.callback.ifPresent(ac -> ac.onComplete(value, Optional.empty())); + synchronized (lock) { + lock.notifyAll(); + } + } + + void setException(Exception exception) { + this.exception = exception; + this.state = FAILED; + this.callback.ifPresent(ac -> ac.onComplete(null, Optional.of(exception))); + synchronized (lock) { + lock.notifyAll(); + } + } + + @Override + public boolean isCompleted() { + return (state > RUNNING); + } + + @Override + public T getValue() throws ExecutionException { + if (state == COMPLETED) { + return value; + } else if (state == FAILED) { + throw new ExecutionException(exception); + } else { + throw new IllegalStateException("Execution not completed yet"); + } + } + + @Override + public void await() throws InterruptedException { + synchronized (lock) { + if (!isCompleted()) { + lock.wait(); + } + } + } + } +} diff --git a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java new file mode 100644 index 000000000..989fa6d1d --- /dev/null +++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java @@ -0,0 +1,13 @@ +package com.iluwatar.async.method.invocation; + +import org.junit.Test; + +public class AppTest { + + @Test + public void test() throws Exception { + String[] args = {}; + App.main(args); + } + +} diff --git a/pom.xml b/pom.xml index aa484f15a..93bb137b4 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,7 @@ naked-objects front-controller repository + async-method-invocation From 5cc83b85b404bf961f535104d23377347e7aec62 Mon Sep 17 00:00:00 2001 From: Tapio Rautonen Date: Tue, 21 Jul 2015 00:28:24 +0300 Subject: [PATCH 5/7] #68: Javadocs. --- .../iluwatar/async/method/invocation/App.java | 52 +++++++++++++++++++ .../method/invocation/AsyncCallback.java | 6 +++ .../method/invocation/AsyncExecutor.java | 23 ++++++++ .../async/method/invocation/AsyncResult.java | 17 ++++++ .../invocation/ThreadAsyncExecutor.java | 24 +++++++++ 5 files changed, 122 insertions(+) diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java index f59f831b2..de4dfe926 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java @@ -2,30 +2,76 @@ package com.iluwatar.async.method.invocation; import java.util.concurrent.Callable; +/** + *

+ * This application demonstrates the async method invocation pattern. Key parts of the pattern are + * AsyncResult which is an intermediate container for an asynchronously evaluated value, + * AsyncCallback which can be provided to be executed on task completion and + * AsyncExecutor that manages the execution of the async tasks. + *

+ *

+ * The main method shows example flow of async invocations. The main thread starts multiple tasks with + * variable durations and then continues its own work. When the main thread has done it's job it collects + * the results of the async tasks. Two of the tasks are handled with callbacks, meaning the callbacks are + * executed immediately when the tasks complete. + *

+ *

+ * Noteworthy difference of thread usage between the async results and callbacks is that the async results + * are collected in the main thread but the callbacks are executed within the worker threads. This should be + * noted when working with thread pools. + *

+ *

+ * Java provides its own implementations of async method invocation pattern. FutureTask, CompletableFuture + * and ExecutorService are the real world implementations of this pattern. But due to the nature of parallel + * programming, the implementations are not trivial. This example does not take all possible scenarios into + * account but rather provides a simple version that helps to understand the pattern. + *

+ * + * @see AsyncResult + * @see AsyncCallback + * @see AsyncExecutor + * + * @see java.util.concurrent.FutureTask + * @see java.util.concurrent.CompletableFuture + * @see java.util.concurrent.ExecutorService + */ public class App { public static void main(String[] args) throws Exception { + // construct a new executor that will run async tasks AsyncExecutor executor = new ThreadAsyncExecutor(); + + // start few async tasks with varying processing times, two last with callback handlers AsyncResult asyncResult1 = executor.startProcess(lazyval(10, 500)); AsyncResult asyncResult2 = executor.startProcess(lazyval("test", 300)); AsyncResult asyncResult3 = executor.startProcess(lazyval(50L, 700)); AsyncResult asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4")); AsyncResult asyncResult5 = executor.startProcess(lazyval("callback", 600), callback("Callback result 5")); + // emulate processing in the current thread while async tasks are running in their own threads Thread.sleep(350); // Oh boy I'm working hard here log("Some hard work done"); + // wait for completion of the tasks Integer result1 = executor.endProcess(asyncResult1); String result2 = executor.endProcess(asyncResult2); Long result3 = executor.endProcess(asyncResult3); asyncResult4.await(); asyncResult5.await(); + // log the results of the tasks, callbacks log immediately when complete log("Result 1: " + result1); log("Result 2: " + result2); log("Result 3: " + result3); } + /** + * Creates a callable that lazily evaluates to given value with artificial delay. + * + * @param value value to evaluate + * @param delayMillis artificial delay in milliseconds + * @return new callable for lazy evaluation + */ private static Callable lazyval(T value, long delayMillis) { return () -> { Thread.sleep(delayMillis); @@ -34,6 +80,12 @@ public class App { }; } + /** + * Creates a simple callback that logs the complete status of the async result. + * + * @param name callback name + * @return new async callback + */ private static AsyncCallback callback(String name) { return (value, ex) -> { if (ex.isPresent()) { diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java index 0477f70e1..067b79d43 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncCallback.java @@ -4,6 +4,12 @@ import java.util.Optional; public interface AsyncCallback { + /** + * Complete handler which is executed when async task is completed or fails execution. + * + * @param value the evaluated value from async task, undefined when execution fails + * @param ex empty value if execution succeeds, some exception if executions fails + */ void onComplete(T value, Optional ex); } diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java index dd23a8bfb..4bf837a4f 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java @@ -5,10 +5,33 @@ import java.util.concurrent.ExecutionException; public interface AsyncExecutor { + /** + * Starts processing of an async task. Returns immediately with async result. + * + * @param task task to be executed asynchronously + * @return async result for the task + */ AsyncResult startProcess(Callable task); + /** + * Starts processing of an async task. Returns immediately with async result. Executes callback + * when the task is completed. + * + * @param task task to be executed asynchronously + * @param callback callback to be executed on task completion + * @return async result for the task + */ AsyncResult startProcess(Callable task, AsyncCallback callback); + /** + * Ends processing of an async task. Blocks the current thread if necessary and returns the + * evaluated value of the completed task. + * + * @param asyncResult async result of a task + * @return evaluated value of the completed task + * @throws ExecutionException if execution has failed, containing the root cause + * @throws InterruptedException if the execution is interrupted + */ T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException; } diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java index 5bf6145b8..689095e9d 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java @@ -4,9 +4,26 @@ import java.util.concurrent.ExecutionException; public interface AsyncResult { + /** + * Status of the async task execution. + * + * @return true if execution is completed or failed + */ boolean isCompleted(); + /** + * Gets the value of completed async task. + * + * @return evaluated value or throws ExecutionException if execution has failed + * @throws ExecutionException if execution has failed, containing the root cause + * @throws IllegalStateException if execution is not completed + */ T getValue() throws ExecutionException; + /** + * Blocks the current thread until the async task is completed. + * + * @throws InterruptedException if the execution is interrupted + */ void await() throws InterruptedException; } diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java index b368e284d..18b27c3b6 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java @@ -5,8 +5,12 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; +/** + * Implementation of async executor that creates a new thread for every task. + */ public class ThreadAsyncExecutor implements AsyncExecutor { + /** Index for thread naming */ private final AtomicInteger idx = new AtomicInteger(0); @Override @@ -37,6 +41,14 @@ public class ThreadAsyncExecutor implements AsyncExecutor { } } + /** + * Simple implementation of async result that allows completing it successfully with a value + * or exceptionally with an exception. A really simplified version from its real life cousins + * FutureTask and CompletableFuture. + * + * @see java.util.concurrent.FutureTask + * @see java.util.concurrent.CompletableFuture + */ private static class CompletableResult implements AsyncResult { static final int RUNNING = 1; @@ -55,6 +67,12 @@ public class ThreadAsyncExecutor implements AsyncExecutor { this.callback = Optional.ofNullable(callback); } + /** + * Sets the value from successful execution and executes callback if available. Notifies + * any thread waiting for completion. + * + * @param value value of the evaluated task + */ void setValue(T value) { this.value = value; this.state = COMPLETED; @@ -64,6 +82,12 @@ public class ThreadAsyncExecutor implements AsyncExecutor { } } + /** + * Sets the exception from failed execution and executes callback if available. Notifies + * any thread waiting for completion. + * + * @param exception exception of the failed task + */ void setException(Exception exception) { this.exception = exception; this.state = FAILED; From 6fbac15e6434855ced1c983fc18f8812f1b5b190 Mon Sep 17 00:00:00 2001 From: Tapio Rautonen Date: Wed, 22 Jul 2015 23:18:07 +0300 Subject: [PATCH 6/7] #68: Documented async method invocation. --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 19cc195ef..06ebd1353 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ Concurrency patterns are those types of design patterns that deal with the multi * [Double Checked Locking](#double-checked-locking) * [Thread Pool](#thread-pool) +* [Async Method Invocation](#async-method-invocation) ### Presentation Tier Patterns @@ -619,6 +620,18 @@ validation and for building to order **Applicability:** Use the Thread Pool pattern when * You have a large number of short-lived tasks to be executed in parallel +## Async Method Invocation [↑](#list-of-design-patterns) +**Intent:** Asynchronous method invocation is pattern where the calling thread is not blocked while waiting results of tasks. The pattern provides parallel processing of multiple independent tasks and retrieving the results via callbacks or waiting until everything is done. + +**Applicability:** Use async method invocation pattern when +* You have multiple independent tasks that can run in parallel +* You need to improve performance of running a group of sequential tasks +* You have limited number of processing capacity or long running tasks and the caller cannot wait the tasks to be ready + +**Real world examples:** +* [FutureTask](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/FutureTask.html), [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) and [ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) (Java) +* [Task-based Asynchronous Pattern](https://msdn.microsoft.com/en-us/library/hh873175.aspx) (.NET) + ## Private Class Data [↑](#list-of-design-patterns) **Intent:** Private Class Data design pattern seeks to reduce exposure of attributes by limiting their visibility. It reduces the number of class attributes by encapsulating them in single Data object. From f7bf3955410a7c424f10e324db0338eda9fa7876 Mon Sep 17 00:00:00 2001 From: Tapio Rautonen Date: Wed, 22 Jul 2015 23:38:09 +0300 Subject: [PATCH 7/7] #68: Updated for upstream. --- async-method-invocation/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/async-method-invocation/pom.xml b/async-method-invocation/pom.xml index be932bca5..56d3ffffc 100644 --- a/async-method-invocation/pom.xml +++ b/async-method-invocation/pom.xml @@ -5,7 +5,7 @@ com.iluwatar java-design-patterns - 1.3.0 + 1.4.0 async-method-invocation