X Tutup
Skip to content

Commit a8b7dd5

Browse files
committed
Merge branch 'trautonen-async-method-invocation'
2 parents 0fc009c + 4f0414a commit a8b7dd5

File tree

9 files changed

+353
-0
lines changed

9 files changed

+353
-0
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ Concurrency patterns are those types of design patterns that deal with the multi
7171

7272
* [Double Checked Locking](#double-checked-locking)
7373
* [Thread Pool](#thread-pool)
74+
* [Async Method Invocation](#async-method-invocation)
7475

7576
### Presentation Tier Patterns
7677

@@ -623,6 +624,18 @@ validation and for building to order
623624
**Applicability:** Use the Thread Pool pattern when
624625
* You have a large number of short-lived tasks to be executed in parallel
625626

627+
## <a name="async-method-invocation">Async Method Invocation</a> [&#8593;](#list-of-design-patterns)
628+
**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.
629+
630+
**Applicability:** Use async method invocation pattern when
631+
* You have multiple independent tasks that can run in parallel
632+
* You need to improve performance of running a group of sequential tasks
633+
* You have limited number of processing capacity or long running tasks and the caller cannot wait the tasks to be ready
634+
635+
**Real world examples:**
636+
* [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)
637+
* [Task-based Asynchronous Pattern](https://msdn.microsoft.com/en-us/library/hh873175.aspx) (.NET)
638+
626639
## <a name="private-class-data">Private Class Data</a> [&#8593;](#list-of-design-patterns)
627640
**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.
628641

async-method-invocation/pom.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0"?>
2+
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>com.iluwatar</groupId>
7+
<artifactId>java-design-patterns</artifactId>
8+
<version>1.4.0</version>
9+
</parent>
10+
<artifactId>async-method-invocation</artifactId>
11+
<dependencies>
12+
<dependency>
13+
<groupId>junit</groupId>
14+
<artifactId>junit</artifactId>
15+
<scope>test</scope>
16+
</dependency>
17+
</dependencies>
18+
</project>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.iluwatar.async.method.invocation;
2+
3+
import java.util.concurrent.Callable;
4+
5+
/**
6+
* <p>
7+
* This application demonstrates the async method invocation pattern. Key parts of the pattern are
8+
* <code>AsyncResult</code> which is an intermediate container for an asynchronously evaluated value,
9+
* <code>AsyncCallback</code> which can be provided to be executed on task completion and
10+
* <code>AsyncExecutor</code> that manages the execution of the async tasks.
11+
* </p>
12+
* <p>
13+
* The main method shows example flow of async invocations. The main thread starts multiple tasks with
14+
* variable durations and then continues its own work. When the main thread has done it's job it collects
15+
* the results of the async tasks. Two of the tasks are handled with callbacks, meaning the callbacks are
16+
* executed immediately when the tasks complete.
17+
* </p>
18+
* <p>
19+
* Noteworthy difference of thread usage between the async results and callbacks is that the async results
20+
* are collected in the main thread but the callbacks are executed within the worker threads. This should be
21+
* noted when working with thread pools.
22+
* </p>
23+
* <p>
24+
* Java provides its own implementations of async method invocation pattern. FutureTask, CompletableFuture
25+
* and ExecutorService are the real world implementations of this pattern. But due to the nature of parallel
26+
* programming, the implementations are not trivial. This example does not take all possible scenarios into
27+
* account but rather provides a simple version that helps to understand the pattern.
28+
* </p>
29+
*
30+
* @see AsyncResult
31+
* @see AsyncCallback
32+
* @see AsyncExecutor
33+
*
34+
* @see java.util.concurrent.FutureTask
35+
* @see java.util.concurrent.CompletableFuture
36+
* @see java.util.concurrent.ExecutorService
37+
*/
38+
public class App {
39+
40+
public static void main(String[] args) throws Exception {
41+
// construct a new executor that will run async tasks
42+
AsyncExecutor executor = new ThreadAsyncExecutor();
43+
44+
// start few async tasks with varying processing times, two last with callback handlers
45+
AsyncResult<Integer> asyncResult1 = executor.startProcess(lazyval(10, 500));
46+
AsyncResult<String> asyncResult2 = executor.startProcess(lazyval("test", 300));
47+
AsyncResult<Long> asyncResult3 = executor.startProcess(lazyval(50L, 700));
48+
AsyncResult<Integer> asyncResult4 = executor.startProcess(lazyval(20, 400), callback("Callback result 4"));
49+
AsyncResult<String> asyncResult5 = executor.startProcess(lazyval("callback", 600), callback("Callback result 5"));
50+
51+
// emulate processing in the current thread while async tasks are running in their own threads
52+
Thread.sleep(350); // Oh boy I'm working hard here
53+
log("Some hard work done");
54+
55+
// wait for completion of the tasks
56+
Integer result1 = executor.endProcess(asyncResult1);
57+
String result2 = executor.endProcess(asyncResult2);
58+
Long result3 = executor.endProcess(asyncResult3);
59+
asyncResult4.await();
60+
asyncResult5.await();
61+
62+
// log the results of the tasks, callbacks log immediately when complete
63+
log("Result 1: " + result1);
64+
log("Result 2: " + result2);
65+
log("Result 3: " + result3);
66+
}
67+
68+
/**
69+
* Creates a callable that lazily evaluates to given value with artificial delay.
70+
*
71+
* @param value value to evaluate
72+
* @param delayMillis artificial delay in milliseconds
73+
* @return new callable for lazy evaluation
74+
*/
75+
private static <T> Callable<T> lazyval(T value, long delayMillis) {
76+
return () -> {
77+
Thread.sleep(delayMillis);
78+
log("Task completed with: " + value);
79+
return value;
80+
};
81+
}
82+
83+
/**
84+
* Creates a simple callback that logs the complete status of the async result.
85+
*
86+
* @param name callback name
87+
* @return new async callback
88+
*/
89+
private static <T> AsyncCallback<T> callback(String name) {
90+
return (value, ex) -> {
91+
if (ex.isPresent()) {
92+
log(name + " failed: " + ex.map(Exception::getMessage).orElse(""));
93+
} else {
94+
log(name + ": " + value);
95+
}
96+
};
97+
}
98+
99+
private static void log(String msg) {
100+
System.out.println(String.format("[%1$-10s] - %2$s", Thread.currentThread().getName(), msg));
101+
}
102+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.iluwatar.async.method.invocation;
2+
3+
import java.util.Optional;
4+
5+
public interface AsyncCallback<T> {
6+
7+
/**
8+
* Complete handler which is executed when async task is completed or fails execution.
9+
*
10+
* @param value the evaluated value from async task, undefined when execution fails
11+
* @param ex empty value if execution succeeds, some exception if executions fails
12+
*/
13+
void onComplete(T value, Optional<Exception> ex);
14+
15+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.iluwatar.async.method.invocation;
2+
3+
import java.util.concurrent.Callable;
4+
import java.util.concurrent.ExecutionException;
5+
6+
public interface AsyncExecutor {
7+
8+
/**
9+
* Starts processing of an async task. Returns immediately with async result.
10+
*
11+
* @param task task to be executed asynchronously
12+
* @return async result for the task
13+
*/
14+
<T> AsyncResult<T> startProcess(Callable<T> task);
15+
16+
/**
17+
* Starts processing of an async task. Returns immediately with async result. Executes callback
18+
* when the task is completed.
19+
*
20+
* @param task task to be executed asynchronously
21+
* @param callback callback to be executed on task completion
22+
* @return async result for the task
23+
*/
24+
<T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback);
25+
26+
/**
27+
* Ends processing of an async task. Blocks the current thread if necessary and returns the
28+
* evaluated value of the completed task.
29+
*
30+
* @param asyncResult async result of a task
31+
* @return evaluated value of the completed task
32+
* @throws ExecutionException if execution has failed, containing the root cause
33+
* @throws InterruptedException if the execution is interrupted
34+
*/
35+
<T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException;
36+
37+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.iluwatar.async.method.invocation;
2+
3+
import java.util.concurrent.ExecutionException;
4+
5+
public interface AsyncResult<T> {
6+
7+
/**
8+
* Status of the async task execution.
9+
*
10+
* @return <code>true</code> if execution is completed or failed
11+
*/
12+
boolean isCompleted();
13+
14+
/**
15+
* Gets the value of completed async task.
16+
*
17+
* @return evaluated value or throws ExecutionException if execution has failed
18+
* @throws ExecutionException if execution has failed, containing the root cause
19+
* @throws IllegalStateException if execution is not completed
20+
*/
21+
T getValue() throws ExecutionException;
22+
23+
/**
24+
* Blocks the current thread until the async task is completed.
25+
*
26+
* @throws InterruptedException if the execution is interrupted
27+
*/
28+
void await() throws InterruptedException;
29+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package com.iluwatar.async.method.invocation;
2+
3+
import java.util.Optional;
4+
import java.util.concurrent.Callable;
5+
import java.util.concurrent.ExecutionException;
6+
import java.util.concurrent.atomic.AtomicInteger;
7+
8+
/**
9+
* Implementation of async executor that creates a new thread for every task.
10+
*/
11+
public class ThreadAsyncExecutor implements AsyncExecutor {
12+
13+
/** Index for thread naming */
14+
private final AtomicInteger idx = new AtomicInteger(0);
15+
16+
@Override
17+
public <T> AsyncResult<T> startProcess(Callable<T> task) {
18+
return startProcess(task, null);
19+
}
20+
21+
@Override
22+
public <T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback) {
23+
CompletableResult<T> result = new CompletableResult<>(callback);
24+
new Thread(() -> {
25+
try {
26+
result.setValue(task.call());
27+
} catch (Exception ex) {
28+
result.setException(ex);
29+
}
30+
}, "executor-" + idx.incrementAndGet()).start();
31+
return result;
32+
}
33+
34+
@Override
35+
public <T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException {
36+
if (asyncResult.isCompleted()) {
37+
return asyncResult.getValue();
38+
} else {
39+
asyncResult.await();
40+
return asyncResult.getValue();
41+
}
42+
}
43+
44+
/**
45+
* Simple implementation of async result that allows completing it successfully with a value
46+
* or exceptionally with an exception. A really simplified version from its real life cousins
47+
* FutureTask and CompletableFuture.
48+
*
49+
* @see java.util.concurrent.FutureTask
50+
* @see java.util.concurrent.CompletableFuture
51+
*/
52+
private static class CompletableResult<T> implements AsyncResult<T> {
53+
54+
static final int RUNNING = 1;
55+
static final int FAILED = 2;
56+
static final int COMPLETED = 3;
57+
58+
final Object lock;
59+
final Optional<AsyncCallback<T>> callback;
60+
61+
volatile int state = RUNNING;
62+
T value;
63+
Exception exception;
64+
65+
CompletableResult(AsyncCallback<T> callback) {
66+
this.lock = new Object();
67+
this.callback = Optional.ofNullable(callback);
68+
}
69+
70+
/**
71+
* Sets the value from successful execution and executes callback if available. Notifies
72+
* any thread waiting for completion.
73+
*
74+
* @param value value of the evaluated task
75+
*/
76+
void setValue(T value) {
77+
this.value = value;
78+
this.state = COMPLETED;
79+
this.callback.ifPresent(ac -> ac.onComplete(value, Optional.<Exception>empty()));
80+
synchronized (lock) {
81+
lock.notifyAll();
82+
}
83+
}
84+
85+
/**
86+
* Sets the exception from failed execution and executes callback if available. Notifies
87+
* any thread waiting for completion.
88+
*
89+
* @param exception exception of the failed task
90+
*/
91+
void setException(Exception exception) {
92+
this.exception = exception;
93+
this.state = FAILED;
94+
this.callback.ifPresent(ac -> ac.onComplete(null, Optional.of(exception)));
95+
synchronized (lock) {
96+
lock.notifyAll();
97+
}
98+
}
99+
100+
@Override
101+
public boolean isCompleted() {
102+
return (state > RUNNING);
103+
}
104+
105+
@Override
106+
public T getValue() throws ExecutionException {
107+
if (state == COMPLETED) {
108+
return value;
109+
} else if (state == FAILED) {
110+
throw new ExecutionException(exception);
111+
} else {
112+
throw new IllegalStateException("Execution not completed yet");
113+
}
114+
}
115+
116+
@Override
117+
public void await() throws InterruptedException {
118+
synchronized (lock) {
119+
if (!isCompleted()) {
120+
lock.wait();
121+
}
122+
}
123+
}
124+
}
125+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.iluwatar.async.method.invocation;
2+
3+
import org.junit.Test;
4+
5+
public class AppTest {
6+
7+
@Test
8+
public void test() throws Exception {
9+
String[] args = {};
10+
App.main(args);
11+
}
12+
13+
}

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
<module>naked-objects</module>
7171
<module>front-controller</module>
7272
<module>repository</module>
73+
<module>async-method-invocation</module>
7374
<module>business-delegate</module>
7475
</modules>
7576

0 commit comments

Comments
 (0)
X Tutup