Implemented half sync half async pattern #109
This commit is contained in:
parent
2aa9681b91
commit
960fac5ea0
16
README.md
16
README.md
@ -72,6 +72,7 @@ Concurrency patterns are those types of design patterns that deal with the multi
|
|||||||
* [Double Checked Locking](#double-checked-locking)
|
* [Double Checked Locking](#double-checked-locking)
|
||||||
* [Thread Pool](#thread-pool)
|
* [Thread Pool](#thread-pool)
|
||||||
* [Async Method Invocation](#async-method-invocation)
|
* [Async Method Invocation](#async-method-invocation)
|
||||||
|
* [Half-Sync/Half-Async](#half-sync-half-async)
|
||||||
|
|
||||||
### Presentation Tier Patterns
|
### Presentation Tier Patterns
|
||||||
|
|
||||||
@ -714,7 +715,22 @@ validation and for building to order
|
|||||||
* you want to orchestrate calls to multiple business services
|
* you want to orchestrate calls to multiple business services
|
||||||
* you want to encapsulate service lookups and service calls
|
* you want to encapsulate service lookups and service calls
|
||||||
|
|
||||||
|
## <a name="half-sync-half-async">Half-Sync/Half-Async</a> [↑](#list-of-design-patterns)
|
||||||
|
**Intent:** The Half-Sync/Half-Async pattern decouples synchronous I/O from asynchronous I/O in a system to simplify concurrent programming effort without degrading execution efficiency.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Applicability:** Use Half-Sync/Half-Async pattern when
|
||||||
|
* A system possesses following characteristics:
|
||||||
|
* System must perform tasks in response to external events that occur asynchronously, like hardware interrupts in OS
|
||||||
|
* It is inefficient to dedicate separate thread of control to perform synchronous I/O for each external source of event
|
||||||
|
* The higher level tasks in the system can be simplified significantly if I/O is performed synchronously.
|
||||||
|
* One or more tasks in a system must run in a single thread of control, while other tasks may benefit from multi-threading.
|
||||||
|
|
||||||
|
**Real world examples:**
|
||||||
|
* [BSD Unix networking subsystem](http://www.cs.wustl.edu/~schmidt/PDF/PLoP-95.pdf)
|
||||||
|
* [Real Time CORBA](http://www.omg.org/news/meetings/workshops/presentations/realtime2001/4-3_Pyarali_thread-pool.pdf)
|
||||||
|
* [Android AsyncTask framework](http://developer.android.com/reference/android/os/AsyncTask.html)
|
||||||
|
|
||||||
# Frequently asked questions
|
# Frequently asked questions
|
||||||
|
|
||||||
|
BIN
half-sync-half-async/etc/half-sync-half-async.png
Normal file
BIN
half-sync-half-async/etc/half-sync-half-async.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
77
half-sync-half-async/etc/half-sync-half-async.ucls
Normal file
77
half-sync-half-async/etc/half-sync-half-async.ucls
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
|
||||||
|
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
|
||||||
|
<class id="1" language="java" name="com.iluwatar.halfsynchalfasync.AsynchronousService" project="half-sync-half-async"
|
||||||
|
file="/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java" binary="false"
|
||||||
|
corner="BOTTOM_RIGHT">
|
||||||
|
<position height="115" width="265" x="41" y="37"/>
|
||||||
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
|
sort-features="false" accessors="true" visibility="true">
|
||||||
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
</display>
|
||||||
|
</class>
|
||||||
|
<interface id="2" language="java" name="com.iluwatar.halfsynchalfasync.AsyncTask" project="half-sync-half-async"
|
||||||
|
file="/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsyncTask.java" binary="false"
|
||||||
|
corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="776" y="223"/>
|
||||||
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
|
sort-features="false" accessors="true" visibility="true">
|
||||||
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
</display>
|
||||||
|
</interface>
|
||||||
|
<interface id="3" language="java" name="java.util.concurrent.BlockingQueue" project="async-method-invocation"
|
||||||
|
file="/opt/Softwares/Eclipses/MARS/eclipse/jre/lib/rt.jar" binary="true" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="494" y="310"/>
|
||||||
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
|
sort-features="false" accessors="true" visibility="true">
|
||||||
|
<attributes public="false" package="false" protected="false" private="false" static="true"/>
|
||||||
|
<operations public="false" package="false" protected="false" private="false" static="true"/>
|
||||||
|
</display>
|
||||||
|
</interface>
|
||||||
|
<class id="4" language="java" name="java.util.concurrent.ThreadPoolExecutor" project="async-method-invocation"
|
||||||
|
file="/opt/Softwares/Eclipses/MARS/eclipse/jre/lib/rt.jar" binary="true" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="174" y="343"/>
|
||||||
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
|
sort-features="false" accessors="true" visibility="true">
|
||||||
|
<attributes public="true" package="true" protected="true" private="false" static="true"/>
|
||||||
|
<operations public="false" package="false" protected="false" private="false" static="false"/>
|
||||||
|
</display>
|
||||||
|
</class>
|
||||||
|
<interface id="5" language="java" name="java.util.concurrent.Callable" project="async-method-invocation"
|
||||||
|
file="/opt/Softwares/Eclipses/MARS/eclipse/jre/lib/rt.jar" binary="true" corner="BOTTOM_RIGHT">
|
||||||
|
<position height="-1" width="-1" x="772" y="47"/>
|
||||||
|
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
|
sort-features="false" accessors="true" visibility="true">
|
||||||
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
</display>
|
||||||
|
</interface>
|
||||||
|
<generalization id="6">
|
||||||
|
<end type="SOURCE" refId="2"/>
|
||||||
|
<end type="TARGET" refId="5"/>
|
||||||
|
</generalization>
|
||||||
|
<association id="7">
|
||||||
|
<end type="SOURCE" refId="4" navigable="false">
|
||||||
|
<attribute id="8" name="workQueue"/>
|
||||||
|
<multiplicity id="9" minimum="0" maximum="1"/>
|
||||||
|
</end>
|
||||||
|
<end type="TARGET" refId="3" navigable="true"/>
|
||||||
|
<display labels="true" multiplicity="true"/>
|
||||||
|
</association>
|
||||||
|
<dependency id="10">
|
||||||
|
<end type="SOURCE" refId="1"/>
|
||||||
|
<end type="TARGET" refId="3"/>
|
||||||
|
</dependency>
|
||||||
|
<dependency id="11">
|
||||||
|
<end type="SOURCE" refId="1"/>
|
||||||
|
<end type="TARGET" refId="4"/>
|
||||||
|
</dependency>
|
||||||
|
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||||
|
sort-features="false" accessors="true" visibility="true">
|
||||||
|
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||||
|
</classifier-display>
|
||||||
|
<association-display labels="true" multiplicity="true"/>
|
||||||
|
</class-diagram>
|
18
half-sync-half-async/pom.xml
Normal file
18
half-sync-half-async/pom.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<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"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.iluwatar</groupId>
|
||||||
|
<artifactId>java-design-patterns</artifactId>
|
||||||
|
<version>1.5.0</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>half-sync-half-async</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -0,0 +1,118 @@
|
|||||||
|
package com.iluwatar.halfsynchalfasync;
|
||||||
|
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This application demonstrates Half-Sync/Half-Async pattern. Key parts of the pattern are
|
||||||
|
* {@link AsyncTask} and {@link AsynchronousService}.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <i>PROBLEM</i>
|
||||||
|
* <br/>
|
||||||
|
* A concurrent system have a mixture of short duration, mid duration and long duration tasks.
|
||||||
|
* Mid or long duration tasks should be performed asynchronously to meet quality of service
|
||||||
|
* requirements.
|
||||||
|
*
|
||||||
|
* <p><i>INTENT</i>
|
||||||
|
* <br/>
|
||||||
|
* The intent of this pattern is to separate the the synchronous and asynchronous processing
|
||||||
|
* in the concurrent application by introducing two intercommunicating layers - one for sync
|
||||||
|
* and one for async. This simplifies the programming without unduly affecting the performance.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <i>APPLICABILITY</i>
|
||||||
|
* <br/>
|
||||||
|
* <ul>
|
||||||
|
* <li>UNIX network subsystems - In operating systems network operations are carried out
|
||||||
|
* asynchronously with help of hardware level interrupts.</li>
|
||||||
|
* <li>CORBA - At the asynchronous layer one thread is associated with each socket that is
|
||||||
|
* connected to the client. Thread blocks waiting for CORBA requests from the client. On receiving
|
||||||
|
* request it is inserted in the queuing layer which is then picked up by synchronous layer which
|
||||||
|
* processes the request and sends response back to the client.</li>
|
||||||
|
* <li>Android AsyncTask framework - Framework provides a way to execute long running blocking calls,
|
||||||
|
* such as downloading a file, in background threads so that the UI thread remains free to respond
|
||||||
|
* to user inputs.</i>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <i>IMPLEMENTATION</i>
|
||||||
|
* <br/>
|
||||||
|
* The main method creates an asynchronous service which does not block the main thread while
|
||||||
|
* the task is being performed. The main thread continues its work which is similar to Async Method
|
||||||
|
* Invocation pattern. The difference between them is that there is a queuing layer between Asynchronous
|
||||||
|
* layer and synchronous layer, which allows for different communication patterns between both layers.
|
||||||
|
* Such as Priority Queue can be used as queuing layer to prioritize the way tasks are executed.
|
||||||
|
* Our implementation is just one simple way of implementing this pattern, there are many variants possible
|
||||||
|
* as described in its applications.
|
||||||
|
*/
|
||||||
|
public class App {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
AsynchronousService service = new AsynchronousService(new LinkedBlockingQueue<>());
|
||||||
|
/*
|
||||||
|
* A new task to calculate sum is received but as this is main thread, it should not block.
|
||||||
|
* So it passes it to the asynchronous task layer to compute and proceeds with handling other
|
||||||
|
* incoming requests. This is particularly useful when main thread is waiting on Socket to receive
|
||||||
|
* new incoming requests and does not wait for particular request to be completed before responding
|
||||||
|
* to new request.
|
||||||
|
*/
|
||||||
|
service.execute(new ArithmeticSumTask(1000));
|
||||||
|
|
||||||
|
/* New task received, lets pass that to async layer for computation. So both requests will be
|
||||||
|
* executed in parallel.
|
||||||
|
*/
|
||||||
|
service.execute(new ArithmeticSumTask(500));
|
||||||
|
service.execute(new ArithmeticSumTask(2000));
|
||||||
|
service.execute(new ArithmeticSumTask(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ArithmeticSumTask implements AsyncTask<Long> {
|
||||||
|
private long n;
|
||||||
|
|
||||||
|
public ArithmeticSumTask(long n) {
|
||||||
|
this.n = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the long running task that is performed in background. In our example
|
||||||
|
* the long running task is calculating arithmetic sum with artificial delay.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Long call() throws Exception {
|
||||||
|
return ap(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This will be called in context of the main thread where some validations can be
|
||||||
|
* done regarding the inputs. Such as it must be greater than 0. It's a small
|
||||||
|
* computation which can be performed in main thread. If we did validated the input
|
||||||
|
* in background thread then we pay the cost of context switching
|
||||||
|
* which is much more than validating it in main thread.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onPreCall() {
|
||||||
|
if (n < 0) {
|
||||||
|
throw new IllegalArgumentException("n is less than 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPostCall(Long result) {
|
||||||
|
// Handle the result of computation
|
||||||
|
System.out.println(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable throwable) {
|
||||||
|
throw new IllegalStateException("Should not occur");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long ap(long i) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(i);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
return (i) * (i + 1) / 2;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.iluwatar.halfsynchalfasync;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents some computation that is performed asynchronously and its result.
|
||||||
|
* The computation is typically done is background threads and the result is posted
|
||||||
|
* back in form of callback. The callback does not implement {@code isComplete}, {@code cancel}
|
||||||
|
* as it is out of scope of this pattern.
|
||||||
|
*
|
||||||
|
* @param <O> type of result
|
||||||
|
*/
|
||||||
|
public interface AsyncTask<O> extends Callable<O> {
|
||||||
|
/**
|
||||||
|
* Is called in context of caller thread before call to {@link #call()}. Large
|
||||||
|
* tasks should not be performed in this method as it will block the caller thread.
|
||||||
|
* Small tasks such as validations can be performed here so that the performance penalty
|
||||||
|
* of context switching is not incurred in case of invalid requests.
|
||||||
|
*/
|
||||||
|
void onPreCall();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback called after the result is successfully computed by {@link #call()}. In our
|
||||||
|
* implementation this method is called in context of background thread but in some variants,
|
||||||
|
* such as Android where only UI thread can change the state of UI widgets, this method is called
|
||||||
|
* in context of UI thread.
|
||||||
|
*/
|
||||||
|
void onPostCall(O result);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback called if computing the task resulted in some exception. This method
|
||||||
|
* is called when either of {@link #call()} or {@link #onPreCall()} throw any exception.
|
||||||
|
*
|
||||||
|
* @param throwable error cause
|
||||||
|
*/
|
||||||
|
void onError(Throwable throwable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is where the computation of task should reside. This method is called in context
|
||||||
|
* of background thread.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
O call() throws Exception;
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package com.iluwatar.halfsynchalfasync;
|
||||||
|
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.FutureTask;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the asynchronous layer which does not block when a new request arrives. It just passes
|
||||||
|
* the request to the synchronous layer which consists of a queue i.e. a {@link BlockingQueue} and
|
||||||
|
* a pool of threads i.e. {@link ThreadPoolExecutor}. Out of this pool of worker threads one of the
|
||||||
|
* thread picks up the task and executes it synchronously in background and the result is posted back
|
||||||
|
* to the caller via callback.
|
||||||
|
*/
|
||||||
|
public class AsynchronousService {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This represents the queuing layer as well as synchronous layer of the pattern. The thread
|
||||||
|
* pool contains worker threads which execute the tasks in blocking/synchronous manner. Long
|
||||||
|
* running tasks should be performed in the background which does not affect the performance of
|
||||||
|
* main thread.
|
||||||
|
*/
|
||||||
|
private ExecutorService service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an asynchronous service using {@code workQueue} as communication channel between
|
||||||
|
* asynchronous layer and synchronous layer. Different types of queues such as Priority queue,
|
||||||
|
* can be used to control the pattern of communication between the layers.
|
||||||
|
*/
|
||||||
|
public AsynchronousService(BlockingQueue<Runnable> workQueue) {
|
||||||
|
service = new ThreadPoolExecutor(10, 10, 10, TimeUnit.SECONDS, workQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A non-blocking method which performs the task provided in background and returns immediately.
|
||||||
|
* <p>
|
||||||
|
* On successful completion of task the result is posted back using callback method
|
||||||
|
* {@link AsyncTask#onPostCall(Object)}, if task execution is unable to complete normally
|
||||||
|
* due to some exception then the reason for error is posted back using callback method
|
||||||
|
* {@link AsyncTask#onError(Throwable)}.
|
||||||
|
* <p>
|
||||||
|
* NOTE: The results are posted back in the context of background thread in this implementation.
|
||||||
|
*/
|
||||||
|
public <T> void execute(final AsyncTask<T> task) {
|
||||||
|
try {
|
||||||
|
// some small tasks such as validation can be performed here.
|
||||||
|
task.onPreCall();
|
||||||
|
} catch (Exception e) {
|
||||||
|
task.onError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
service.submit(new FutureTask<T>(task) {
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
super.done();
|
||||||
|
try {
|
||||||
|
/* called in context of background thread. There is other variant possible
|
||||||
|
* where result is posted back and sits in the queue of caller thread which
|
||||||
|
* then picks it up for processing. An example of such a system is Android OS,
|
||||||
|
* where the UI elements can only be updated using UI thread. So result must be
|
||||||
|
* posted back in UI thread.
|
||||||
|
*/
|
||||||
|
task.onPostCall(get());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// should not occur
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
task.onError(e.getCause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.iluwatar.halfsynchalfasync;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class AppTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws InterruptedException, ExecutionException {
|
||||||
|
App.main(null);
|
||||||
|
}
|
||||||
|
}
|
3
pom.xml
3
pom.xml
@ -71,7 +71,8 @@
|
|||||||
<module>front-controller</module>
|
<module>front-controller</module>
|
||||||
<module>repository</module>
|
<module>repository</module>
|
||||||
<module>async-method-invocation</module>
|
<module>async-method-invocation</module>
|
||||||
<module>business-delegate</module>
|
<module>business-delegate</module>
|
||||||
|
<module>half-sync-half-async</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user