BackgroundInitializer.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.apache.commons.lang3.concurrent;
- import java.util.concurrent.Callable;
- import java.util.concurrent.CancellationException;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.Future;
- import org.apache.commons.lang3.function.FailableConsumer;
- import org.apache.commons.lang3.function.FailableSupplier;
- /**
- * A class that allows complex initialization operations in a background task.
- *
- * <p>
- * Applications often have to do some expensive initialization steps when they
- * are started, e.g. constructing a connection to a database, reading a
- * configuration file, etc. Doing these things in parallel can enhance
- * performance as the CPU load can be improved. However, when access to the
- * resources initialized in a background thread is actually required,
- * synchronization has to be performed to ensure that their initialization is
- * complete.
- * </p>
- * <p>
- * This abstract base class provides support for this use case. A concrete
- * subclass must implement the {@link #initialize()} method. Here an arbitrary
- * initialization can be implemented, and a result object can be returned. With
- * this method in place the basic usage of this class is as follows (where
- * {@code MyBackgroundInitializer} is a concrete subclass):
- * </p>
- *
- * <pre>
- * MyBackgroundInitializer initializer = new MyBackgroundInitializer();
- * initializer.start();
- * // Now do some other things. Initialization runs in a parallel thread
- * ...
- * // Wait for the end of initialization and access the result object
- * Object result = initializer.get();
- * </pre>
- *
- * <p>
- * After the construction of a {@link BackgroundInitializer} object its
- * {@link #start()} method has to be called. This starts the background
- * processing. The application can now continue to do other things. When it
- * needs access to the object produced by the {@link BackgroundInitializer} it
- * calls its {@link #get()} method. If initialization is already complete,
- * {@link #get()} returns the result object immediately. Otherwise it blocks
- * until the result object is fully constructed.
- * </p>
- * <p>
- * {@link BackgroundInitializer} is a thin wrapper around a {@link Future}
- * object and uses an {@link ExecutorService} for running the background
- * initialization task. It is possible to pass in an {@link ExecutorService} at
- * construction time or set one using {@code setExternalExecutor()} before
- * {@code start()} was called. Then this object is used to spawn the background
- * task. If no {@link ExecutorService} has been provided, {@code
- * BackgroundInitializer} creates a temporary {@link ExecutorService} and
- * destroys it when initialization is complete.
- * </p>
- * <p>
- * The methods provided by {@link BackgroundInitializer} provide for minimal
- * interaction with the wrapped {@link Future} object. It is also possible to
- * obtain the {@link Future} object directly. Then the enhanced functionality
- * offered by {@link Future} can be used, e.g. to check whether the background
- * operation is complete or to cancel the operation.
- * </p>
- *
- * @since 3.0
- * @param <T> the type of the object managed by this initializer class
- */
- public class BackgroundInitializer<T> extends AbstractConcurrentInitializer<T, Exception> {
- /**
- * Builds a new instance.
- *
- * @param <T> the type of the object managed by the initializer.
- * @param <I> the type of the initializer managed by this builder.
- * @since 3.14.0
- */
- public static class Builder<I extends BackgroundInitializer<T>, T> extends AbstractBuilder<I, T, Builder<I, T>, Exception> {
- /**
- * The external executor service for executing tasks. null is a permitted value.
- */
- private ExecutorService externalExecutor;
- /**
- * Constructs a new instance.
- */
- public Builder() {
- // empty
- }
- @SuppressWarnings("unchecked")
- @Override
- public I get() {
- return (I) new BackgroundInitializer(getInitializer(), getCloser(), externalExecutor);
- }
- /**
- * Sets the external executor service for executing tasks. null is a permitted value.
- *
- * @see org.apache.commons.lang3.concurrent.BackgroundInitializer#setExternalExecutor(ExecutorService)
- *
- * @param externalExecutor the {@link ExecutorService} to be used.
- * @return {@code this} instance.
- */
- public Builder<I, T> setExternalExecutor(final ExecutorService externalExecutor) {
- this.externalExecutor = externalExecutor;
- return asThis();
- }
- }
- private final class InitializationTask implements Callable<T> {
- /** Stores the executor service to be destroyed at the end. */
- private final ExecutorService execFinally;
- /**
- * Creates a new instance of {@link InitializationTask} and initializes
- * it with the {@link ExecutorService} to be destroyed at the end.
- *
- * @param exec the {@link ExecutorService}
- */
- InitializationTask(final ExecutorService exec) {
- execFinally = exec;
- }
- /**
- * Initiates initialization and returns the result.
- *
- * @return the result object
- * @throws Exception if an error occurs
- */
- @Override
- public T call() throws Exception {
- try {
- return initialize();
- } finally {
- if (execFinally != null) {
- execFinally.shutdown();
- }
- }
- }
- }
- /**
- * Creates a new builder.
- *
- * @param <T> the type of object to build.
- * @return a new builder.
- * @since 3.14.0
- */
- public static <T> Builder<BackgroundInitializer<T>, T> builder() {
- return new Builder<>();
- }
- /** The external executor service for executing tasks. */
- private ExecutorService externalExecutor; // @GuardedBy("this")
- /** A reference to the executor service that is actually used. */
- private ExecutorService executor; // @GuardedBy("this")
- /** Stores the handle to the background task. */
- private Future<T> future; // @GuardedBy("this")
- /**
- * Creates a new instance of {@link BackgroundInitializer}. No external
- * {@link ExecutorService} is used.
- */
- protected BackgroundInitializer() {
- this(null);
- }
- /**
- * Creates a new instance of {@link BackgroundInitializer} and initializes
- * it with the given {@link ExecutorService}. If the {@link ExecutorService}
- * is not null, the background task for initializing this object will be
- * scheduled at this service. Otherwise a new temporary {@code
- * ExecutorService} is created.
- *
- * @param exec an external {@link ExecutorService} to be used for task
- * execution
- */
- protected BackgroundInitializer(final ExecutorService exec) {
- setExternalExecutor(exec);
- }
- /**
- * Constructs a new instance.
- *
- * @param initializer the initializer supplier called by {@link #initialize()}.
- * @param closer the closer consumer called by {@link #close()}.
- * @param exec the {@link ExecutorService} to be used @see #setExternalExecutor(ExecutorService)
- */
- private BackgroundInitializer(final FailableSupplier<T, ConcurrentException> initializer, final FailableConsumer<T, ConcurrentException> closer, final ExecutorService exec) {
- super(initializer, closer);
- setExternalExecutor(exec);
- }
- /**
- * Creates the {@link ExecutorService} to be used. This method is called if
- * no {@link ExecutorService} was provided at construction time.
- *
- * @return the {@link ExecutorService} to be used
- */
- private ExecutorService createExecutor() {
- return Executors.newFixedThreadPool(getTaskCount());
- }
- /**
- * Creates a task for the background initialization. The {@link Callable}
- * object returned by this method is passed to the {@link ExecutorService}.
- * This implementation returns a task that invokes the {@link #initialize()}
- * method. If a temporary {@link ExecutorService} is used, it is destroyed
- * at the end of the task.
- *
- * @param execDestroy the {@link ExecutorService} to be destroyed by the
- * task
- * @return a task for the background initialization
- */
- private Callable<T> createTask(final ExecutorService execDestroy) {
- return new InitializationTask(execDestroy);
- }
- /**
- * Returns the result of the background initialization. This method blocks
- * until initialization is complete. If the background processing caused a
- * runtime exception, it is directly thrown by this method. Checked
- * exceptions, including {@link InterruptedException} are wrapped in a
- * {@link ConcurrentException}. Calling this method before {@link #start()}
- * was called causes an {@link IllegalStateException} exception to be
- * thrown.
- *
- * @return the object produced by this initializer
- * @throws ConcurrentException if a checked exception occurred during
- * background processing
- * @throws IllegalStateException if {@link #start()} has not been called
- */
- @Override
- public T get() throws ConcurrentException {
- try {
- return getFuture().get();
- } catch (final ExecutionException execex) {
- ConcurrentUtils.handleCause(execex);
- return null; // should not be reached
- } catch (final InterruptedException iex) {
- // reset interrupted state
- Thread.currentThread().interrupt();
- throw new ConcurrentException(iex);
- }
- }
- /**
- * Returns the {@link ExecutorService} that is actually used for executing
- * the background task. This method can be called after {@link #start()}
- * (before {@code start()} it returns <b>null</b>). If an external executor
- * was set, this is also the active executor. Otherwise this method returns
- * the temporary executor that was created by this object.
- *
- * @return the {@link ExecutorService} for executing the background task
- */
- protected final synchronized ExecutorService getActiveExecutor() {
- return executor;
- }
- /**
- * Returns the external {@link ExecutorService} to be used by this class.
- *
- * @return the {@link ExecutorService}
- */
- public final synchronized ExecutorService getExternalExecutor() {
- return externalExecutor;
- }
- /**
- * Returns the {@link Future} object that was created when {@link #start()}
- * was called. Therefore this method can only be called after {@code
- * start()}.
- *
- * @return the {@link Future} object wrapped by this initializer
- * @throws IllegalStateException if {@link #start()} has not been called
- */
- public synchronized Future<T> getFuture() {
- if (future == null) {
- throw new IllegalStateException("start() must be called first!");
- }
- return future;
- }
- /**
- * Returns the number of background tasks to be created for this
- * initializer. This information is evaluated when a temporary {@code
- * ExecutorService} is created. This base implementation returns 1. Derived
- * classes that do more complex background processing can override it. This
- * method is called from a synchronized block by the {@link #start()}
- * method. Therefore overriding methods should be careful with obtaining
- * other locks and return as fast as possible.
- *
- * @return the number of background tasks required by this initializer
- */
- protected int getTaskCount() {
- return 1;
- }
- /**
- * {@inheritDoc}
- */
- @Override
- protected Exception getTypedException(final Exception e) {
- //This Exception object will be used for type comparison in AbstractConcurrentInitializer.initialize but not thrown
- return new Exception(e);
- }
- /**
- * Tests whether this instance is initialized. Once initialized, always returns true.
- * If initialization failed then the failure will be cached and this will never return
- * true.
- *
- * @return true if initialization completed successfully, otherwise false
- * @since 3.14.0
- */
- @Override
- public synchronized boolean isInitialized() {
- if (future == null || ! future.isDone() ) {
- return false;
- }
- try {
- future.get();
- return true;
- } catch (CancellationException | ExecutionException | InterruptedException e) {
- return false;
- }
- }
- /**
- * Returns a flag whether this {@link BackgroundInitializer} has already
- * been started.
- *
- * @return a flag whether the {@link #start()} method has already been
- * called
- */
- public synchronized boolean isStarted() {
- return future != null;
- }
- /**
- * Sets an {@link ExecutorService} to be used by this class. The {@code
- * ExecutorService} passed to this method is used for executing the
- * background task. Thus it is possible to re-use an already existing
- * {@link ExecutorService} or to use a specially configured one. If no
- * {@link ExecutorService} is set, this instance creates a temporary one and
- * destroys it after background initialization is complete. Note that this
- * method must be called before {@link #start()}; otherwise an exception is
- * thrown.
- *
- * @param externalExecutor the {@link ExecutorService} to be used
- * @throws IllegalStateException if this initializer has already been
- * started
- */
- public final synchronized void setExternalExecutor(
- final ExecutorService externalExecutor) {
- if (isStarted()) {
- throw new IllegalStateException(
- "Cannot set ExecutorService after start()!");
- }
- this.externalExecutor = externalExecutor;
- }
- /**
- * Starts the background initialization. With this method the initializer
- * becomes active and invokes the {@link #initialize()} method in a
- * background task. A {@link BackgroundInitializer} can be started exactly
- * once. The return value of this method determines whether the start was
- * successful: only the first invocation of this method returns <b>true</b>,
- * following invocations will return <b>false</b>.
- *
- * @return a flag whether the initializer could be started successfully
- */
- public synchronized boolean start() {
- // Not yet started?
- if (!isStarted()) {
- // Determine the executor to use and whether a temporary one has to
- // be created
- final ExecutorService tempExec;
- executor = getExternalExecutor();
- if (executor == null) {
- executor = tempExec = createExecutor();
- } else {
- tempExec = null;
- }
- future = executor.submit(createTask(tempExec));
- return true;
- }
- return false;
- }
- }