Add method ConcurrentInitializer#isInitialized() (#1120)
* use NO_INIT to better handle initialize returning null * add isInitialized method to all ConcurrentInitializer classes and test * Update AtomicInitializer.java * Update AtomicSafeInitializer.java --------- Co-authored-by: Gary Gregory <garydgregory@users.noreply.github.com>
This commit is contained in:
parent
77973e55a1
commit
02132ee97b
|
@ -36,4 +36,13 @@ public abstract class AbstractConcurrentInitializer<T, E extends Exception> impl
|
|||
*/
|
||||
protected abstract T initialize() throws E;
|
||||
|
||||
/**
|
||||
* Returns true if initialization has been completed. If initialization threw an exception this will return false, but it will return true if a subsequent
|
||||
* call to initialize completes successfully. If the implementation of ConcurrentInitializer can initialize multiple objects, this will only return true if
|
||||
* all objects have been initialized.
|
||||
*
|
||||
* @return true if all initialization is complete, otherwise false
|
||||
*/
|
||||
protected abstract boolean isInitialized();
|
||||
|
||||
}
|
||||
|
|
|
@ -63,8 +63,10 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
* @param <T> the type of the object managed by this initializer class
|
||||
*/
|
||||
public abstract class AtomicInitializer<T> extends AbstractConcurrentInitializer<T, RuntimeException> {
|
||||
|
||||
private static final Object NO_INIT = new Object();
|
||||
/** Holds the reference to the managed object. */
|
||||
private final AtomicReference<T> reference = new AtomicReference<>();
|
||||
private final AtomicReference<T> reference = new AtomicReference<>((T) NO_INIT);
|
||||
|
||||
/**
|
||||
* Returns the object managed by this initializer. The object is created if
|
||||
|
@ -79,9 +81,9 @@ public abstract class AtomicInitializer<T> extends AbstractConcurrentInitializer
|
|||
public T get() throws ConcurrentException {
|
||||
T result = reference.get();
|
||||
|
||||
if (result == null) {
|
||||
if (result == (T) NO_INIT) {
|
||||
result = initialize();
|
||||
if (!reference.compareAndSet(null, result)) {
|
||||
if (!reference.compareAndSet((T) NO_INIT, result)) {
|
||||
// another thread has initialized the reference
|
||||
result = reference.get();
|
||||
}
|
||||
|
@ -89,4 +91,15 @@ public abstract class AtomicInitializer<T> extends AbstractConcurrentInitializer
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether this instance is initialized. Once initialized, always returns true.
|
||||
*
|
||||
* @return whether this instance is initialized. Once initialized, always returns true.
|
||||
* @since 3.14.0
|
||||
*/
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return reference.get() != NO_INIT;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,12 +53,13 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
*/
|
||||
public abstract class AtomicSafeInitializer<T> extends AbstractConcurrentInitializer<T, RuntimeException> {
|
||||
|
||||
private static final Object NO_INIT = new Object();
|
||||
/** A guard which ensures that initialize() is called only once. */
|
||||
private final AtomicReference<AtomicSafeInitializer<T>> factory =
|
||||
new AtomicReference<>();
|
||||
|
||||
/** Holds the reference to the managed object. */
|
||||
private final AtomicReference<T> reference = new AtomicReference<>();
|
||||
private final AtomicReference<T> reference = new AtomicReference<>((T) NO_INIT);
|
||||
|
||||
/**
|
||||
* Gets (and initialize, if not initialized yet) the required object
|
||||
|
@ -71,7 +72,7 @@ public abstract class AtomicSafeInitializer<T> extends AbstractConcurrentInitial
|
|||
public final T get() throws ConcurrentException {
|
||||
T result;
|
||||
|
||||
while ((result = reference.get()) == null) {
|
||||
while ((result = reference.get()) == (T) NO_INIT) {
|
||||
if (factory.compareAndSet(null, this)) {
|
||||
reference.set(initialize());
|
||||
}
|
||||
|
@ -79,4 +80,15 @@ public abstract class AtomicSafeInitializer<T> extends AbstractConcurrentInitial
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether this instance is initialized. Once initialized, always returns true.
|
||||
*
|
||||
* @return whether this instance is initialized. Once initialized, always returns true.
|
||||
* @since 3.14.0
|
||||
*/
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return reference.get() != NO_INIT;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
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;
|
||||
|
@ -123,6 +124,28 @@ public abstract class BackgroundInitializer<T> extends AbstractConcurrentInitial
|
|||
return externalExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
|
|
@ -80,6 +80,17 @@ public class ConstantInitializer<T> implements ConcurrentInitializer<T> {
|
|||
return getObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* As a {@link ConstantInitializer} is initialized on construction this will
|
||||
* always return true.
|
||||
*
|
||||
* @return true.
|
||||
* @since 3.14.0
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash code for this object. This implementation returns the hash
|
||||
* code of the managed object.
|
||||
|
|
|
@ -199,6 +199,22 @@ public class MultiBackgroundInitializer
|
|||
return new MultiBackgroundInitializerResults(inits, results, excepts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether this all child {@code BackgroundInitializer} objects are initialized.
|
||||
* Once initialized, always returns true.
|
||||
*
|
||||
* @return whether all child {@code BackgroundInitializer} objects instance are initialized. Once initialized, always returns true. If there are no child {@code BackgroundInitializer} objects return false.
|
||||
* @since 3.14.0
|
||||
*/
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
if (childInitializers.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return childInitializers.values().stream().allMatch(BackgroundInitializer::isInitialized);
|
||||
}
|
||||
|
||||
/**
|
||||
* A data class for storing the results of the background initialization
|
||||
* performed by {@link MultiBackgroundInitializer}. Objects of this inner
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
package org.apache.commons.lang3.concurrent;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
|
@ -46,6 +48,20 @@ public abstract class AbstractConcurrentInitializerTest extends AbstractLangTest
|
|||
assertNotNull(createInitializer().get(), "No managed object");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a simple invocation of the isInitialized() method.
|
||||
*/
|
||||
@Test
|
||||
public void testisInitialized() throws Throwable {
|
||||
final ConcurrentInitializer<Object> initializer = createInitializer();
|
||||
if (initializer instanceof AbstractConcurrentInitializer) {
|
||||
AbstractConcurrentInitializer castedInitializer = (AbstractConcurrentInitializer) initializer;
|
||||
assertFalse(castedInitializer.isInitialized(), "was initialized before get()");
|
||||
assertNotNull(castedInitializer.get(), "No managed object");
|
||||
assertTrue(castedInitializer.isInitialized(), "was not initialized after get()");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether sequential get() invocations always return the same
|
||||
* instance.
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
*/
|
||||
package org.apache.commons.lang3.concurrent;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Test class for {@code AtomicInitializer}.
|
||||
*/
|
||||
|
@ -34,4 +40,23 @@ public class AtomicInitializerTest extends AbstractConcurrentInitializerTest {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetThatReturnsNullFirstTime() throws ConcurrentException {
|
||||
final AtomicInitializer<Object> initializer = new AtomicInitializer<Object>() {
|
||||
final AtomicBoolean firstRun = new AtomicBoolean(true);
|
||||
|
||||
@Override
|
||||
protected Object initialize() {
|
||||
if (firstRun.getAndSet(false)) {
|
||||
return null;
|
||||
} else {
|
||||
return new Object();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
assertNull(initializer.get());
|
||||
assertNull(initializer.get());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
package org.apache.commons.lang3.concurrent;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -58,6 +60,25 @@ public class AtomicSafeInitializerTest extends AbstractConcurrentInitializerTest
|
|||
assertEquals(1, initializer.initCounter.get(), "Wrong number of invocations");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetThatReturnsNullFirstTime() throws ConcurrentException {
|
||||
final AtomicSafeInitializer<Object> initializer = new AtomicSafeInitializer<Object>() {
|
||||
final AtomicBoolean firstRun = new AtomicBoolean(true);
|
||||
|
||||
@Override
|
||||
protected Object initialize() {
|
||||
if (firstRun.getAndSet(false)) {
|
||||
return null;
|
||||
} else {
|
||||
return new Object();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
assertNull(initializer.get());
|
||||
assertNull(initializer.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* A concrete test implementation of {@code AtomicSafeInitializer} which also serves as a simple example.
|
||||
* <p>
|
||||
|
|
|
@ -267,6 +267,21 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
|||
assertTrue(init.isStarted(), "Not started");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests isInitialized() before and after the background task has finished.
|
||||
*/
|
||||
@Test
|
||||
public void testIsInitialized() throws ConcurrentException {
|
||||
final BackgroundInitializerTestImpl init = new BackgroundInitializerTestImpl();
|
||||
init.enableLatch();
|
||||
init.start();
|
||||
assertTrue(init.isStarted(), "Not started"); //Started and Initialized should return opposite values
|
||||
assertFalse(init.isInitialized(), "Initalized before releasing latch");
|
||||
init.releaseLatch();
|
||||
init.get(); //to ensure the initialize thread has completed.
|
||||
assertTrue(init.isInitialized(), "Not initalized after releasing latch");
|
||||
}
|
||||
|
||||
/**
|
||||
* A concrete implementation of BackgroundInitializer. It also overloads
|
||||
* some methods that simplify testing.
|
||||
|
@ -282,6 +297,10 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
|||
/** The number of invocations of initialize(). */
|
||||
volatile int initializeCalls;
|
||||
|
||||
/** A latch tests can use to control when initialize completes. */
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
boolean waitForLatch = false;
|
||||
|
||||
BackgroundInitializerTestImpl() {
|
||||
}
|
||||
|
||||
|
@ -289,6 +308,14 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
|||
super(exec);
|
||||
}
|
||||
|
||||
public void enableLatch() {
|
||||
waitForLatch = true;
|
||||
}
|
||||
|
||||
public void releaseLatch() {
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Records this invocation. Optionally throws an exception or sleeps a
|
||||
* while.
|
||||
|
@ -303,6 +330,9 @@ public class BackgroundInitializerTest extends AbstractLangTest {
|
|||
if (shouldSleep) {
|
||||
ThreadUtils.sleep(Duration.ofMinutes(1));
|
||||
}
|
||||
if (waitForLatch) {
|
||||
latch.await();
|
||||
}
|
||||
return Integer.valueOf(++initializeCalls);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,4 +130,14 @@ public class ConstantInitializerTest extends AbstractLangTest {
|
|||
final String s = new ConstantInitializer<>(null).toString();
|
||||
assertTrue(s.indexOf("object = null") > 0, "Object not found: " + s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a simple invocation of the isInitialized() method.
|
||||
*/
|
||||
@Test
|
||||
public void testisInitialized() {
|
||||
assertTrue(init.isInitialized(), "was not initialized before get()");
|
||||
assertEquals(VALUE, init.getObject(), "Wrong object");
|
||||
assertTrue(init.isInitialized(), "was not initialized after get()");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
|||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -42,6 +44,9 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
|||
/** The initializer to be tested. */
|
||||
private MultiBackgroundInitializer initializer;
|
||||
|
||||
/** A short time to wait for background threads to run. */
|
||||
private static final long PERIOD_MILLIS = 50;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
initializer = new MultiBackgroundInitializer();
|
||||
|
@ -366,6 +371,43 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
|||
assertTrue(exec.isShutdown(), "Executor not shutdown");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsInitialized()
|
||||
throws ConcurrentException, InterruptedException {
|
||||
final ChildBackgroundInitializer childOne = new ChildBackgroundInitializer();
|
||||
final ChildBackgroundInitializer childTwo = new ChildBackgroundInitializer();
|
||||
|
||||
childOne.enableLatch();
|
||||
childTwo.enableLatch();
|
||||
|
||||
assertFalse(initializer.isInitialized(), "Initalized without having anything to initalize");
|
||||
|
||||
initializer.addInitializer("child one", childOne);
|
||||
initializer.addInitializer("child two", childTwo);
|
||||
initializer.start();
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
long waitTime = 3000;
|
||||
long endTime = startTime + waitTime;
|
||||
//wait for the children to start
|
||||
while (! childOne.isStarted() || ! childTwo.isStarted()) {
|
||||
if (System.currentTimeMillis() > endTime) {
|
||||
fail("children never started");
|
||||
Thread.sleep(PERIOD_MILLIS);
|
||||
}
|
||||
}
|
||||
|
||||
assertFalse(initializer.isInitialized(), "Initalized with two children running");
|
||||
|
||||
childOne.releaseLatch();
|
||||
childOne.get(); //ensure this child finishes initializing
|
||||
assertFalse(initializer.isInitialized(), "Initalized with one child running");
|
||||
|
||||
childTwo.releaseLatch();
|
||||
childTwo.get(); //ensure this child finishes initializing
|
||||
assertTrue(initializer.isInitialized(), "Not initalized with no children running");
|
||||
}
|
||||
|
||||
/**
|
||||
* A concrete implementation of {@code BackgroundInitializer} used for
|
||||
* defining background tasks for {@code MultiBackgroundInitializer}.
|
||||
|
@ -381,6 +423,18 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
|||
/** An exception to be thrown by initialize(). */
|
||||
Exception ex;
|
||||
|
||||
/** A latch tests can use to control when initialize completes. */
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
boolean waitForLatch = false;
|
||||
|
||||
public void enableLatch() {
|
||||
waitForLatch = true;
|
||||
}
|
||||
|
||||
public void releaseLatch() {
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Records this invocation. Optionally throws an exception.
|
||||
*/
|
||||
|
@ -393,6 +447,10 @@ public class MultiBackgroundInitializerTest extends AbstractLangTest {
|
|||
throw ex;
|
||||
}
|
||||
|
||||
if (waitForLatch) {
|
||||
latch.await();
|
||||
}
|
||||
|
||||
return Integer.valueOf(initializeCalls);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue