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 000000000..84658dfde Binary files /dev/null and b/half-sync-half-async/etc/half-sync-half-async.png differ 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 + *
+ *

+ * + *

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