From 960fac5ea004489bd3b9ef8e8029c4f4dd7310d5 Mon Sep 17 00:00:00 2001 From: Narendra Pathai Date: Sat, 25 Jul 2015 15:58:12 +0530 Subject: [PATCH] Implemented half sync half async pattern #109 --- README.md | 16 +++ .../etc/half-sync-half-async.png | Bin 0 -> 32866 bytes .../etc/half-sync-half-async.ucls | 77 ++++++++++++ half-sync-half-async/pom.xml | 18 +++ .../com/iluwatar/halfsynchalfasync/App.java | 118 ++++++++++++++++++ .../iluwatar/halfsynchalfasync/AsyncTask.java | 44 +++++++ .../AsynchronousService.java | 75 +++++++++++ .../iluwatar/halfsynchalfasync/AppTest.java | 13 ++ pom.xml | 3 +- 9 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 half-sync-half-async/etc/half-sync-half-async.png create mode 100644 half-sync-half-async/etc/half-sync-half-async.ucls create mode 100644 half-sync-half-async/pom.xml create mode 100644 half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java create mode 100644 half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsyncTask.java create mode 100644 half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java create mode 100644 half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java diff --git a/README.md b/README.md index 109428295..bf763787a 100644 --- a/README.md +++ b/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) * [Thread Pool](#thread-pool) * [Async Method Invocation](#async-method-invocation) +* [Half-Sync/Half-Async](#half-sync-half-async) ### 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 encapsulate service lookups and service calls +## Half-Sync/Half-Async [↑](#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. +![Half-Sync/Half-Async class diagram](./half-sync-half-async/etc/half-sync-half-async.png) + +**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 diff --git a/half-sync-half-async/etc/half-sync-half-async.png b/half-sync-half-async/etc/half-sync-half-async.png new file mode 100644 index 0000000000000000000000000000000000000000..84658dfdeafcdfd782629c4b55060404d04323a0 GIT binary patch literal 32866 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 literal 0 HcmV?d00001 diff --git a/half-sync-half-async/etc/half-sync-half-async.ucls b/half-sync-half-async/etc/half-sync-half-async.ucls new file mode 100644 index 000000000..5b9941872 --- /dev/null +++ b/half-sync-half-async/etc/half-sync-half-async.ucls @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/half-sync-half-async/pom.xml b/half-sync-half-async/pom.xml new file mode 100644 index 000000000..edbc2e420 --- /dev/null +++ b/half-sync-half-async/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.5.0 + + half-sync-half-async + + + junit + junit + test + + + diff --git a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java new file mode 100644 index 000000000..77b1988ba --- /dev/null +++ b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java @@ -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}. + * + *

+ * PROBLEM + *
+ * 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. + * + *

INTENT + *
+ * 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. + * + *

+ * APPLICABILITY + *
+ *

    + *
  • UNIX network subsystems - In operating systems network operations are carried out + * asynchronously with help of hardware level interrupts.
  • + *
  • 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.
  • + *
  • 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. + *
+ * + *

+ * IMPLEMENTATION + *
+ * 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 { + 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; + } +} diff --git a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsyncTask.java b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsyncTask.java new file mode 100644 index 000000000..8ed7376b6 --- /dev/null +++ b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsyncTask.java @@ -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 type of result + */ +public interface AsyncTask extends Callable { + /** + * 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; +} diff --git a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java new file mode 100644 index 000000000..6c36354d0 --- /dev/null +++ b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java @@ -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 workQueue) { + service = new ThreadPoolExecutor(10, 10, 10, TimeUnit.SECONDS, workQueue); + } + + + /** + * A non-blocking method which performs the task provided in background and returns immediately. + *

+ * 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)}. + *

+ * NOTE: The results are posted back in the context of background thread in this implementation. + */ + public void execute(final AsyncTask task) { + try { + // some small tasks such as validation can be performed here. + task.onPreCall(); + } catch (Exception e) { + task.onError(e); + } + + service.submit(new FutureTask(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()); + } + } + }); + } +} diff --git a/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java new file mode 100644 index 000000000..54f6ea5a7 --- /dev/null +++ b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java @@ -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); + } +} diff --git a/pom.xml b/pom.xml index db468a2fb..13105f139 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,8 @@ front-controller repository async-method-invocation - business-delegate + business-delegate + half-sync-half-async