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