These probably don't qualify as canonical use cases, but they are applications of executors that I have come across in real life.
I have a service interface with a method that blocks, possibly for a long time, for example, the client side of an HTTP-based service that blocks while waiting for a response.
public interface Request ... public interface Response ... interface BlockingService { Response serve(Request req) throws ServiceException; }
I want an adapter that converts this interface into one that
returns a Future<Response>
without blocking.
interface NonBlockingService { Future<Response> serve(Request req); }
Here's how I might do it under the currently checked in proposal.
import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.Future; class NonBlockingServiceAdapter implements NonBlockingService { public NonBlockingServiceAdapter(BlockingService svc) { this(svc, null); } public NonBlockingServiceAdapter(BlockingService svc, Executor executor) { this.blockingService = svc; if (executor == null) { this.executor = Executors.newFixedThreadPool(3); } else { this.executor = executor; } } public Future<Response> serve(final Request req) { Callable<Response> task = new Callable<Response>() { public Response call() { return blockingService.serve(req); } }; return Executors.execute(executor, task); } private final BlockingService blockingService; private final Executor executor; }
If I change my mind about using a fixed size thread pool as the
default and want to use, say, a caching thread pool, I don't have to change
the imports and I don't have to learn any new types; I just
consult the Executors
javadocs and change one line to something
like:
this.executor = Executors.newCachedThreadPool();
In this case it is irrelevant that the factory method returns a more specific type.
If there were no Executor
factory methods or if those factory
methods were part of AbstractExecutor/ThreadExecutor
, I'd have to
learn about and import a type just so I could get access to its
factory methods or public constructors.
If I have more complicated requirements on the internal
executor, I can pass in an instance that has been configured in
advance with Callbacks/Hooks/Intercepts
, possibly an extension of an
existing class or even my own custom implementation of Executor
.
If a later redesign requires lifecycle management for the
internal executor, under the current proposal I would change its
type to ExecutorService
and provide methods on
NonBlockingServiceAdapter
that ultimately call into the
ExecutorService
instance. The executor and the executor
service are the same object under the current proposal, but this is not
a requirement for this example.
An annoying design flaw in one version of the Borland C++ (for Windows) runtime libraries causes a severe crash.if a C++ exception is thrown in DLL code within a thread other than the one in which the DLL was loaded. For JNI users, there are several ways around this problem, including changing the compiler and avoiding the use of C++ exceptions. However, if none of these workarounds are available, the only solution is to make sure that alls calls to native methods implemented by such a DLL are made in the same thread as the one that loaded the DLL.
If you run into this situation repeatedly, as I did, you might
find yourself wanting an adapter class to do the heavy lifting for
you using a dynamic proxy, as follows: For a class NativeImpl
with
native methods implemented by a Borland C++ DLL, define an interface
NativeInterface
consisting of all of the native methods
of NativeImpl
,
and make NativeImpl
extend NativeInterface
. The
adapter uses Proxy.newProxyInstance
to create a new instance of this
interface by implementing the invoke
to execute each method
call as a Callable on a single thread executor that delegates to an
underlying NativeImpl
.
class SingleThreadAdapter { public SingleThreadAdapter(final String library) { this.executor = Executors.newSingleThreadExecutor(); try { Executors.invoke(executor, new Runnable() { public void run () { System.loadLibrary(library); } }, Boolean.TRUE); } catch (ExecutionException e) { // System.loadLibrary does not throw checked exceptions. Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else { throw (Error) cause; } } catch (InterruptedException e) { throw new RuntimeException(e); // what should this really be? } } public <T> T newInstance(Callable<T> creator) throws Exception { try { T instance = Executors.invoke(executor, creator); return (T) Proxy.newProxyInstance( instance.getClass().getClassLoader(), instance.getClass().getInterfaces(), new Handler(instance)); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof Exception) { throw (Exception) cause; } else { throw (Error) cause; } } catch (InterruptedException e) { throw new RuntimeException(e); // what should this really be? } } private class Handler implements InvocationHandler { public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable { try { return Executors.invoke(executor, new Callable<Object>() { public Object call () throws Exception { return method.invoke(instance, args); } }); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof InvocationTargetException) { throw cause.getCause(); } else { throw cause; } } catch (InterruptedException e) { throw new RuntimeException(e); // what should this really be? } } Handler(Object instance) { this.instance = instance; } private final Object instance; } private final Executor executor; }
Typical use of this class would be:
try { SingleThreadAdapter adapter = new SingleThreadAdapter("LegacyEngine.dll"); NativeInterface ni = (NativeInterface) adapter.newInstance(new Callable<NativeInterface>() { public NativeInterface call() { return new NativeImpl(); // may use native calls } }); int i = ni.nativeIntMethod(); } // catch clauses for adapter creation, instance creation, invocation, // interruption...
Even though it is clear that SingleThreadExecutor and no other kind of Executor will be used in the implementation, there is no reason to expose this anywhere except in the SingleThreadAdapter constructor.
The exception handling is a bit tricky. Joe Bowbeer showed me how to do it right, but it took me a while to understand why it was right. I wonder if other users would be similarly confused.