简体   繁体   中英

What's the advantage of using ExecutorService with immediate get()?

I see in the code here a lot of

executorService.submit(() -> {
    // do stuff
}).get();

...and I wonder why is the executorService used like this, submitting something you will immediately get?

Because somebody blindly copied this from Javadoc , which reads " If you would like to immediately block waiting for a task, you can use constructions of the form result = exec.submit(aCallable).get(); "

Apart from that and the slight difference in exception semantics that @Michael mentioned in his comment on the question, there is also a slight difference wrt. interrupting of threads: if // do stuff blocks uninterruptedly you would still be able to interrupt the blocking call to Future.get .

submitting something you will immediately get

Just because you submit the task and 'get' it immediately, that doesn't necessarily mean it will be run immediately by the ExecutorService.

Here's a practical example of how this is useful:

You have a web server, which returns weather data. When an HTTP request comes in, you need to make a connection to a weather API to retrieve the weather data.

However, that weather API only allows two concurrent connections.

One way to solve this problem would be to have an ExecutorService with only two available threads.

Now, no matter how many servlet threads simultaneously submit tasks for execution, you can ensure that only two threads at a time can perform requests against the weather API. The servlet threads will block until the weather API is available to provide the servlet with the requested data.

Calling an immediate get() forces the ExecutorService to process "do stuff" in the executor service's threadpool rather than the local thread, which may for example choke processing if the maximum number of threads allocated to it is small, despite there being a great many threads running to handle requests.

Consider an obvious example of an executor service that has only 1 thread:

ExecutorService executorService = Executors.newSingleThreadExecutor();

and the example code being called from say an HttpRequestHandler .

By using the immediate get() processing of "do stuff" will be serialized , despite there being many simultaneous requests being processes each in their own thread.

Without the wrapper of executorService.submit(...).get() , processing of "do stuff" would be done in parallel in the request handler's thread.

Or by using:

ExecutorService executorService = Executors.newFixedThreadPool(3);

You limit the parallelism of processing to (for example) 3 threads, despite there being a great many requests being processed simultaneously.


Other more subtle effects are possible to via the choice of Thread in which "do stuff" runs. Consider:

ExecutorService executorService = Executors.newSingleThreadExecutor(myThreadFactory);

will make "do stuff" run in a custom type of thread, rather than in the thread type chosen for the HttpRequestHandler .

The only use case that I can think of is when:

  • code that calls it MUST return some result synchronously (ie it's implementing some sync api handling http request or whatnot)
  • the executor that is called is a ForkJoinPool and the task submitted is the RecursiveTask that will internally fork. This way you could use more than one cpu to execute whole task.

Using a .get() call on a Future allows one to abstract the details of how exactly one delivers the result. Excusing the length of the example, if one looks at the .get() method for the below class, one can see that to implement the same sort of timing mechanism in the calling thread would be an excessive amount of boilerplate code. By abstracting it, the calling thread will simply block indefinitely while the worker thread worries about the details of delivering on the promise of its Future

import java.util.Optional;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;

class DecayingRetry<T> implements Future<T> {
    protected final ScheduledExecutorService executor;
    protected final Callable<T> callable;
    private final boolean isOwnExecutor;
    protected ScheduledFuture<?> future;
    @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
    protected Optional<?> result;
    protected boolean isDone;
    protected boolean isCancelled;
    protected final ReentrantLock lock;
    private final double maxDelay;
    private double initialDelay;
    private final TimeUnit timeUnit;


    private DecayingRetry(ScheduledExecutorService executor,
                            boolean isOwnExecutor,
                            Callable<T> callable,
                            long initialDelay,
                            long maxDelay,
                            TimeUnit timeUnit) {
        this.isOwnExecutor = isOwnExecutor;
        lock = new ReentrantLock(true);
        lock.lock();
        this.executor = executor;
        this.callable = callable;
        isCancelled = false;
        isDone = false;
        if (maxDelay < 0) {
            this.maxDelay = Double.POSITIVE_INFINITY;
        } else {
            this.maxDelay = maxDelay;
        }
        lock.lock();
        this.initialDelay = (double) initialDelay;
        this.timeUnit = timeUnit;
        future = executor.schedule(this::delayLoop, initialDelay, timeUnit);

    }


    public static <T> T on(Callable<T> callable, long initialDelay, long maxDelay, TimeUnit timeUnit) throws Exception {
        try {
            return Optional.ofNullable(callable.call()).orElseThrow(IllegalStateException::new);
        } catch (IllegalStateException ignored) {
            try {
                return new DecayingRetry<>(Executors.newSingleThreadScheduledExecutor(),
                                                      true,
                                                      callable,
                                                      initialDelay,
                                                      maxDelay,
                                                      timeUnit).get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                System.exit(-1);
            }
        }
        return null;
    }

    public static <T> T on(Callable<T> callable,
                           ScheduledExecutorService executor,
                           long initialDelay,
                           long maxDelay,
                           TimeUnit timeUnit) throws Exception {
        try {
            return Optional.ofNullable(callable.call()).orElseThrow(IllegalStateException::new);
        } catch (IllegalStateException ignored) {
            try {
                return new DecayingRetry<>(executor,
                                                      false,
                                                      callable,
                                                      initialDelay,
                                                      maxDelay,
                                                      timeUnit).get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                System.exit(-1);
            }
        }
        return null;
    }

    synchronized private void delayLoop() {
        if (isDone) {
            return;
        }
        try {
            result = Optional.ofNullable(callable.call());
        } catch (Exception e) {
            result = Optional.of(e);
            isDone = true;
            return;
        }
        if (!result.isPresent()) {
            if (initialDelay < maxDelay) {
                initialDelay *= 1.618033988749; //PHI
                initialDelay = Math.min(maxDelay, initialDelay);
            }
            future = executor.schedule(this::delayLoop, (long) initialDelay, timeUnit);
        } else {
            isDone = true;
            lock.unlock();
        }
    }


    public boolean cancel(boolean mayInterruptIfRunning) {
        if (isDone) {
            return false;
        } else if (future.cancel(mayInterruptIfRunning)) {
            isCancelled = true;
            isDone = true;
            return true;
        }
        return false;
    }

    @Override
    public boolean isCancelled() {
        return isCancelled;
    }

    @Override
    public boolean isDone() {
        return isDone;
    }

    @Override
    @NotNull
    public T get() throws InterruptedException, ExecutionException {
        lock.lock();
        while (!isDone) { // lock acquired too early for some reason, so we allow the worker thread to grab it
            lock.unlock();
            lock.lock();
        }
        if (result.isPresent()) {
            if (result.get() instanceof Throwable) {
                throw new ExecutionException((Throwable) result.get());
            }
            if (isOwnExecutor) {
                executor.shutdown();
            }
            //noinspection unchecked
            return (T) result.get();
        }
        throw new ExecutionException(new IllegalStateException("Retry result was null"));
    }

    public T get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
        throw new ExecutionException(new IllegalStateException("Not implemented"));
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM