aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/apache/commons/lang3/concurrent/BackgroundInitializer.java
blob: c74e14afa808f72d64ce843838f40866c7c0d9e7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
/*
 * 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.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 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 abstract class BackgroundInitializer<T> implements
        ConcurrentInitializer<T> {
    /** 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);
    }

    /**
     * Returns the external {@link ExecutorService} to be used by this class.
     *
     * @return the {@link ExecutorService}
     */
    public final synchronized ExecutorService getExternalExecutor() {
        return externalExecutor;
    }

    /**
     * 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;
    }

    /**
     * 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 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 {@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 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;
    }

    /**
     * Performs the initialization. This method is called in a background task
     * when this {@link BackgroundInitializer} is started. It must be
     * implemented by a concrete subclass. An implementation is free to perform
     * arbitrary initialization. The object returned by this method can be
     * queried using the {@link #get()} method.
     *
     * @return a result object
     * @throws Exception if an error occurs
     */
    protected abstract T initialize() throws Exception;

    /**
     * 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);
    }

    /**
     * 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());
    }

    private 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();
                }
            }
        }
    }
}