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 index 1b9026c8b..d773ff93f 100644 --- 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 @@ -3,27 +3,39 @@ package com.iluwatar.halfsynchalfasync; import java.util.concurrent.Callable; /** - * Represents some computation that is performed asynchronously. The computation is typically - * done is background threads and the result is posted back in form of callback. + * 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()}. - * Validations can be performed here so that the performance penalty of context - * switching is not incurred. + * Is called in context of caller thread before call to {@link #call()}. Large + * tasks should not be performed in this method. Validations can be performed here + * so that the performance penalty of context switching is not incurred in case of + * invalid requests. */ void preExecute(); /** - * Is a callback which is called after the result is successfully computed by - * {@link #call()}. + * A callback called after the result is successfully computed by {@link #call()}. */ void onResult(O result); + /** + * A callback called if computing the task resulted in some exception. This method + * is called when either of {@link #call()} or {@link #preExecute()} 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 index 1be5941c5..8b858747e 100644 --- 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 @@ -1,37 +1,77 @@ package com.iluwatar.halfsynchalfasync; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Future; +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 threads one of the thread - * picks up the task and executes it in background and the result is posted back to the caller via - * {@link Future}. + * 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 is the synchronous layer to which request to do work is delegated. + * 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 SynchronousLayer syncLayer; - - public AsynchronousService(QueuingLayer queuingLayer) { - this.syncLayer = new SynchronousLayer(queuingLayer); + 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#onResult(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. + * There is other variant possible where the result is posted back in the queue of caller thread + * and then the result is processed in context of caller thread. */ - public void execute(final AsyncTask task) { - /* - * This is the key part of this pattern where the caller thread does not block until - * the result of work is computed but is delegated to the synchronous layer which - * computes the task in background. This is useful if caller thread is an UI thread, - * which MUST remain responsive to user inputs. - */ - syncLayer.execute(task); + public void execute(final AsyncTask task) { + try { + // some small tasks such as validation can be performed here. + task.preExecute(); + } 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.onResult(get()); + } catch (InterruptedException e) { + // should not occur + } catch (ExecutionException e) { + task.onError(e.getCause()); + } + } + }); } } diff --git a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/QueuingLayer.java b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/QueuingLayer.java deleted file mode 100644 index 82515e393..000000000 --- a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/QueuingLayer.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.iluwatar.halfsynchalfasync; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -public class QueuingLayer { - BlockingQueue incomingQueue = new LinkedBlockingQueue<>(); -} diff --git a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/SynchronousLayer.java b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/SynchronousLayer.java deleted file mode 100644 index 42d329f8a..000000000 --- a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/SynchronousLayer.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.iluwatar.halfsynchalfasync; - -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 represents the Queuing and Synchronous layer of Half-Sync/Half-Async pattern. - * The {@link ThreadPoolExecutor} plays role of both Queuing layer as well as Synchronous layer - * of the pattern, where incoming tasks are queued if no worker is available. - */ -public class SynchronousLayer { - - /* - * This is the synchronous layer where background threads execute the work. - */ - private ExecutorService service; - - /** - * Creates synchronous layer which uses queuing layer to wait for incoming tasks to execute. - */ - public SynchronousLayer(QueuingLayer queuingLayer) { - service = new ThreadPoolExecutor(10, 10, 10, TimeUnit.SECONDS, queuingLayer.incomingQueue); - } - /** - * Submit new work for backgrounds threads to compute - * @return the result after executing the work - */ - public void execute(final AsyncTask work) { - work.preExecute(); - - service.submit(new FutureTask(work) { - @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. - */ - work.onResult(get()); - } catch (InterruptedException e) { - // should not occur - } catch (ExecutionException e) { - work.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/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java deleted file mode 100644 index 4a9976da1..000000000 --- a/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.iluwatar.halfsynchalfasync; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import java.util.concurrent.ExecutionException; - -import org.junit.Test; - -public class AsynchronousServiceTest { - - @Test - public void test() throws InterruptedException, ExecutionException { - /* - * Addition service is asynchronous layer which does not block on single request, - * and is always available for listening new requests. - */ - QueuingLayer queuingLayer = new QueuingLayer(); - new SynchronousLayer(queuingLayer); - AsynchronousService service = new AsynchronousService(queuingLayer); - - service.execute(new ArithmeticSumTask(100)); - service.execute(new ArithmeticSumTask(50)); - service.execute(new ArithmeticSumTask(200)); - service.execute(new ArithmeticSumTask(5)); - } - - class ArithmeticSumTask implements AsyncTask { - private long n; - - public ArithmeticSumTask(long n) { - this.n = n; - } - - @Override - public Long call() throws Exception { - return ap(n); - } - - @Override - public void preExecute() { - if (n < 0) { - throw new IllegalArgumentException("n is less than 0"); - } - } - - @Override - public void onResult(Long result) { - assertEquals(ap(n), result.longValue()); - } - - @Override - public void onError(Throwable throwable) { - fail("Should not occur"); - } - } - - private long ap(long i) { - long out = (i) * (i + 1) / 2; - return out; - } -}