LANG-609: Added AtomicInitializer class. Introduced new ConcurrentInitializer interface which is now implemented by all all initializer classes.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@929189 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oliver Heger 2010-03-30 16:49:22 +00:00
parent e4789bd4fc
commit f96d4df26e
9 changed files with 445 additions and 66 deletions

View File

@ -0,0 +1,106 @@
/*
* 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.atomic.AtomicReference;
/**
* <p>
* A specialized implementation of the {@code ConcurrentInitializer} interface
* based on an {@link AtomicReference} variable.
* </p>
* <p>
* This class maintains a member field of type {@code AtomicReference}. It
* implements the following algorithm to create and initialize an object in its
* {@link #get()} method:
* <ul>
* <li>First it is checked whether the {@code AtomicReference} variable contains
* already a value. If this is the case, the value is directly returned.</li>
* <li>Otherwise the {@link #initialize()} method is called. This method must be
* defined in concrete subclasses to actually create the managed object.</li>
* <li>After the object was created by {@link #initialize()} it is checked
* whether the {@code AtomicReference} variable is still undefined. This has to
* be done because in the meantime another thread may have initialized the
* object. If the reference is still empty, the newly created object is stored
* in it and returned by this method.</li>
* <li>Otherwise the value stored in the {@code AtomicReference} is returned.</li>
* </ul>
* </p>
* <p>
* Because atomic variables are used this class does not need any
* synchronization. So there is no danger of deadlock, and access to the managed
* object is efficient. However, if multiple threads access the {@code
* AtomicInitializer} object before it has been initialized almost at the same
* time, it can happen that {@link #initialize()} is called multiple times. The
* algorithm outlined above guarantees that {@link #get()} always returns the
* same object though.
* </p>
* <p>
* Compared with the {@link LazyInitializer} class, this class can be more
* efficient because it does not need synchronization. The drawback is that the
* {@link #initialize()} method can be called multiple times which may be
* problematic if the creation of the managed object is expensive. As a rule of
* thumb this initializer implementation is preferable if there are not too many
* threads involved and the probability that multiple threads access an
* uninitialized object is small. If there is high parallelism,
* {@link LazyInitializer} is more appropriate.
* </p>
*
* @author Apache Software Foundation
* @version $Id$
* @param <T> the type of the object managed by this initializer class
*/
public abstract class AtomicInitializer<T> implements ConcurrentInitializer<T> {
/** Holds the reference to the managed object. */
private final AtomicReference<T> reference = new AtomicReference<T>();
/**
* Returns the object managed by this initializer. The object is created if
* it is not available yet and stored internally. This method always returns
* the same object.
*
* @return the object created by this {@code AtomicInitializer}
* @throws ConcurrentException if an error occurred during initialization of
* the object
*/
public T get() throws ConcurrentException {
T result = reference.get();
if (result == null) {
result = initialize();
if (!reference.compareAndSet(null, result)) {
// another thread has initialized the reference
result = reference.get();
}
}
return result;
}
/**
* Creates and initializes the object managed by this {@code
* AtomicInitializer}. This method is called by {@link #get()} when the
* managed object is not available yet. An implementation can focus on the
* creation of the object. No synchronization is needed, as this is already
* handled by {@code get()}. As stated by the class comment, it is possible
* that this method is called multiple times.
*
* @return the managed data object
* @throws ConcurrentException if an error occurs during object creation
*/
protected abstract T initialize() throws ConcurrentException;
}

View File

@ -82,7 +82,8 @@
* @version $Id$
* @param <T> the type of the object managed by this initializer class
*/
public abstract class BackgroundInitializer<T> {
public abstract class BackgroundInitializer<T> implements
ConcurrentInitializer<T> {
/** The external executor service for executing tasks. */
private ExecutorService externalExecutor;

View File

@ -0,0 +1,54 @@
/*
* 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;
/**
* <p>
* Definition of an interface for the thread-safe initialization of objects.
* </p>
* <p>
* The idea behind this interface is to provide access to an object in a
* thread-safe manner. A {@code ConcurrentInitializer} can be passed to multiple
* threads which can all access the object produced by the initializer. Through
* the {@link #getInitializedObject()} method the object can be queried.
* </p>
* <p>
* Concrete implementations of this interface will use different strategies for
* the creation of the managed object, e.g. lazy initialization or
* initialization in a background thread. This is completely transparent to
* client code, so it is possible to change the initialization strategy without
* affecting clients.
* </p>
*
* @author Apache Software Foundation
* @version $Id$
* @param <T> the type of the object managed by this initializer class
*/
public interface ConcurrentInitializer<T> {
/**
* Returns the fully initialized object produced by this {@code
* ConcurrentInitializer}. A concrete implementation here returns the
* results of the initialization process. This method may block until
* results are available. Typically, once created the result object is
* always the same.
*
* @return the object created by this {@code ConcurrentException}
* @throws ConcurrentException if an error occurred during initialization of
* the object
*/
T get() throws ConcurrentException;
}

View File

@ -164,6 +164,46 @@ private static void throwCause(ExecutionException ex) {
}
}
//-----------------------------------------------------------------------
/**
* Invokes the specified {@code ConcurrentInitializer} and returns the
* object produced by the initializer. This method just invokes the {@code
* get()} method of the given {@code ConcurrentInitializer}. It is
* <b>null</b>-safe: if the argument is <b>null</b>, result is also
* <b>null</b>.
*
* @param <T> the type of the object produced by the initializer
* @param initializer the {@code ConcurrentInitializer} to be invoked
* @return the object managed by the {@code ConcurrentInitializer}
* @throws ConcurrentException if the {@code ConcurrentInitializer} throws
* an exception
*/
public static <T> T initialize(ConcurrentInitializer<T> initializer)
throws ConcurrentException {
return (initializer != null) ? initializer.get() : null;
}
/**
* Invokes the specified {@code ConcurrentInitializer} and transforms
* occurring exceptions to runtime exceptions. This method works like
* {@link #initialize(ConcurrentInitializer)}, but if the {@code
* ConcurrentInitializer} throws a {@link ConcurrentException}, it is
* caught, and the cause is wrapped in a {@link ConcurrentRuntimeException}.
* So client code does not have to deal with checked exceptions.
*
* @param <T> the type of the object produced by the initializer
* @param initializer the {@code ConcurrentInitializer} to be invoked
* @return the object managed by the {@code ConcurrentInitializer}
* @throws ConcurrentRuntimeException if the initializer throws an exception
*/
public static <T> T initializeUnchecked(ConcurrentInitializer<T> initializer) {
try {
return initialize(initializer);
} catch (ConcurrentException cex) {
throw new ConcurrentRuntimeException(cex.getCause());
}
}
//-----------------------------------------------------------------------
/**
* <p>

View File

@ -76,7 +76,7 @@
* @version $Id$
* @param <T> the type of the object managed by this initializer class
*/
public abstract class LazyInitializer<T> {
public abstract class LazyInitializer<T> implements ConcurrentInitializer<T> {
/** Stores the managed object. */
private volatile T object;
@ -85,8 +85,10 @@ public abstract class LazyInitializer<T> {
* is created. After that it is cached and can be accessed pretty fast.
*
* @return the object initialized by this {@code LazyInitializer}
* @throws ConcurrentException if an error occurred during initialization of
* the object
*/
public T get() {
public T get() throws ConcurrentException {
// use a temporary variable to reduce the number of reads of the
// volatile field
T result = object;
@ -111,6 +113,7 @@ public T get() {
* handled by {@code get()}.
*
* @return the managed data object
* @throws ConcurrentException if an error occurs during object creation
*/
protected abstract T initialize();
protected abstract T initialize() throws ConcurrentException;
}

View File

@ -0,0 +1,116 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.concurrent.CountDownLatch;
import org.junit.Test;
/**
* <p>
* An abstract base class for tests of concrete {@code ConcurrentInitializer}
* implementations.
* </p>
* <p>
* This class provides some basic tests for initializer implementations. Derived
* class have to create a {@link ConcurrentInitializer} object on which the
* tests are executed.
* </p>
*
* @author Apache Software Foundation
* @version $Id$
*/
public abstract class AbstractConcurrentInitializerTest {
/**
* Tests a simple invocation of the get() method.
*/
@Test
public void testGet() throws ConcurrentException {
assertNotNull("No managed object", createInitializer().get());
}
/**
* Tests whether sequential get() invocations always return the same
* instance.
*/
@Test
public void testGetMultipleTimes() throws ConcurrentException {
ConcurrentInitializer<Object> initializer = createInitializer();
Object obj = initializer.get();
for (int i = 0; i < 10; i++) {
assertEquals("Got different object at " + i, obj, initializer.get());
}
}
/**
* Tests whether get() can be invoked from multiple threads concurrently.
* Always the same object should be returned.
*/
@Test
public void testGetConcurrent() throws ConcurrentException,
InterruptedException {
final ConcurrentInitializer<Object> initializer = createInitializer();
final int threadCount = 20;
final CountDownLatch startLatch = new CountDownLatch(1);
class GetThread extends Thread {
Object object;
@Override
public void run() {
try {
// wait until all threads are ready for maximum parallelism
startLatch.await();
// access the initializer
object = initializer.get();
} catch (InterruptedException iex) {
// ignore
} catch (ConcurrentException cex) {
object = cex;
}
}
}
GetThread[] threads = new GetThread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new GetThread();
threads[i].start();
}
// fire all threads and wait until they are ready
startLatch.countDown();
for (Thread t : threads) {
t.join();
}
// check results
Object managedObject = initializer.get();
for (GetThread t : threads) {
assertEquals("Wrong object", managedObject, t.object);
}
}
/**
* Creates the {@link ConcurrentInitializer} object to be tested. This
* method is called whenever the test fixture needs to be obtained.
*
* @return the initializer object to be tested
*/
protected abstract ConcurrentInitializer<Object> createInitializer();
}

View File

@ -0,0 +1,40 @@
/*
* 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;
/**
* Test class for {@code AtomicInitializer}.
*
* @author Apache Software Foundation
* @version $Id$
*/
public class AtomicInitializerTest extends AbstractConcurrentInitializerTest {
/**
* Returns the initializer to be tested.
*
* @return the {@code AtomicInitializer}
*/
@Override
protected ConcurrentInitializer<Object> createInitializer() {
return new AtomicInitializer<Object>() {
@Override
protected Object initialize() throws ConcurrentException {
return new Object();
}
};
}
}

View File

@ -25,6 +25,7 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.easymock.EasyMock;
import org.junit.Test;
/**
@ -301,6 +302,75 @@ public void testHandleCauseUncheckedNull() throws ConcurrentException {
null));
}
//-----------------------------------------------------------------------
/**
* Tests initialize() for a null argument.
*/
@Test
public void testInitializeNull() throws ConcurrentException {
assertNull("Got a result", ConcurrentUtils.initialize(null));
}
/**
* Tests a successful initialize() operation.
*/
@Test
public void testInitialize() throws ConcurrentException {
@SuppressWarnings("unchecked")
ConcurrentInitializer<Object> init = EasyMock
.createMock(ConcurrentInitializer.class);
final Object result = new Object();
EasyMock.expect(init.get()).andReturn(result);
EasyMock.replay(init);
assertSame("Wrong result object", result, ConcurrentUtils
.initialize(init));
EasyMock.verify(init);
}
/**
* Tests initializeUnchecked() for a null argument.
*/
@Test
public void testInitializeUncheckedNull() {
assertNull("Got a result", ConcurrentUtils.initializeUnchecked(null));
}
/**
* Tests a successful initializeUnchecked() operation.
*/
@Test
public void testInitializeUnchecked() throws ConcurrentException {
@SuppressWarnings("unchecked")
ConcurrentInitializer<Object> init = EasyMock
.createMock(ConcurrentInitializer.class);
final Object result = new Object();
EasyMock.expect(init.get()).andReturn(result);
EasyMock.replay(init);
assertSame("Wrong result object", result, ConcurrentUtils
.initializeUnchecked(init));
EasyMock.verify(init);
}
/**
* Tests whether exceptions are correctly handled by initializeUnchecked().
*/
@Test
public void testInitializeUncheckedEx() throws ConcurrentException {
@SuppressWarnings("unchecked")
ConcurrentInitializer<Object> init = EasyMock
.createMock(ConcurrentInitializer.class);
final Exception cause = new Exception();
EasyMock.expect(init.get()).andThrow(new ConcurrentException(cause));
EasyMock.replay(init);
try {
ConcurrentUtils.initializeUnchecked(init);
fail("Exception not thrown!");
} catch (ConcurrentRuntimeException crex) {
assertSame("Wrong cause", cause, crex.getCause());
}
EasyMock.verify(init);
}
//-----------------------------------------------------------------------
/**
* Tests constant future.

View File

@ -16,82 +16,31 @@
*/
package org.apache.commons.lang3.concurrent;
import java.util.concurrent.CountDownLatch;
import junit.framework.TestCase;
import org.junit.Before;
/**
* Test class for {@code LazyInitializer}.
*
* @version $Id$
*/
public class LazyInitializerTest extends TestCase {
public class LazyInitializerTest extends AbstractConcurrentInitializerTest {
/** The initializer to be tested. */
private LazyInitializerTestImpl initializer;
@Override
protected void setUp() throws Exception {
super.setUp();
@Before
public void setUp() throws Exception {
initializer = new LazyInitializerTestImpl();
}
/**
* Tests obtaining the managed object.
* Returns the initializer to be tested. This implementation returns the
* {@code LazyInitializer} created in the {@code setUp()} method.
*
* @return the initializer to be tested
*/
public void testGet() {
assertNotNull("No managed object", initializer.get());
}
/**
* Tests whether sequential get() invocations always return the same
* instance.
*/
public void testGetMultipleTimes() {
Object obj = initializer.get();
for (int i = 0; i < 10; i++) {
assertEquals("Got different object at " + i, obj, initializer.get());
}
}
/**
* Tests invoking get() from multiple threads concurrently.
*/
public void testGetConcurrent() throws InterruptedException {
final int threadCount = 20;
final CountDownLatch startLatch = new CountDownLatch(1);
class GetThread extends Thread {
Object object;
@Override
public void run() {
try {
// wait until all threads are ready for maximum parallelism
startLatch.await();
// access the initializer
object = initializer.get();
} catch (InterruptedException iex) {
// ignore
}
}
}
GetThread[] threads = new GetThread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new GetThread();
threads[i].start();
}
// fire all threads and wait until they are ready
startLatch.countDown();
for (Thread t : threads) {
t.join();
}
// check results
Object managedObject = initializer.get();
for (GetThread t : threads) {
assertEquals("Wrong object", managedObject, t.object);
}
@Override
protected ConcurrentInitializer<Object> createInitializer() {
return initializer;
}
/**