ThreadLocalRandomSource.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.rng.simple;
import java.util.EnumMap;
import java.util.Map;
import org.apache.commons.rng.UniformRandomProvider;
/**
* This class provides a thread-local {@link UniformRandomProvider}.
*
* <p>The {@link UniformRandomProvider} is created once-per-thread using the default
* construction method {@link RandomSource#create()}.
*
* <p>Example:</p>
* <pre><code>
* import org.apache.commons.rng.simple.RandomSource;
* import org.apache.commons.rng.simple.ThreadLocalRandomSource;
* import org.apache.commons.rng.sampling.distribution.PoissonSampler;
*
* // Access a thread-safe random number generator
* UniformRandomProvider rng = ThreadLocalRandomSource.current(RandomSource.SPLIT_MIX_64);
*
* // One-time Poisson sample
* double mean = 12.3;
* int counts = PoissonSampler.of(rng, mean).sample();
* </code></pre>
*
* <p>Note if the {@link RandomSource} requires additional arguments then it is not
* supported. The same can be achieved using:</p>
*
* <pre><code>
* import org.apache.commons.rng.simple.RandomSource;
* import org.apache.commons.rng.sampling.distribution.PoissonSampler;
*
* // Provide a thread-safe random number generator with data arguments
* private static ThreadLocal<UniformRandomProvider> rng =
* new ThreadLocal<UniformRandomProvider>() {
* @Override
* protected UniformRandomProvider initialValue() {
* return RandomSource.TWO_CMRES_SELECT.create(null, 3, 4);
* }
* };
*
* // One-time Poisson sample using a thread-safe random number generator
* double mean = 12.3;
* int counts = PoissonSampler.of(rng.get(), mean).sample();
* </code></pre>
*
* @since 1.3
*/
public final class ThreadLocalRandomSource {
/**
* A map containing the {@link ThreadLocal} instance for each {@link RandomSource}.
*
* <p>This should only be modified to create new instances in a synchronized block.
*/
private static final Map<RandomSource, ThreadLocal<UniformRandomProvider>> SOURCES =
new EnumMap<>(RandomSource.class);
/** No public construction. */
private ThreadLocalRandomSource() {}
/**
* Extend the {@link ThreadLocal} to allow creation of the desired {@link RandomSource}.
*/
private static class ThreadLocalRng extends ThreadLocal<UniformRandomProvider> {
/** The source. */
private final RandomSource source;
/**
* Create a new instance.
*
* @param source the source
*/
ThreadLocalRng(RandomSource source) {
this.source = source;
}
@Override
protected UniformRandomProvider initialValue() {
// Create with the default seed generation method
return source.create();
}
}
/**
* Returns the current thread's copy of the given {@code source}. If there is no
* value for the current thread, it is first initialized to the value returned
* by {@link RandomSource#create()}.
*
* <p>Note if the {@code source} requires additional arguments then it is not
* supported.
*
* @param source the source
* @return the current thread's value of the {@code source}.
* @throws IllegalArgumentException if the source is null or the source requires arguments
*/
public static UniformRandomProvider current(RandomSource source) {
ThreadLocal<UniformRandomProvider> rng = SOURCES.get(source);
// Implement double-checked locking:
// https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
if (rng == null) {
// Do the checks on the source here since it is an edge case
// and the EnumMap handles null (returning null).
if (source == null) {
throw new IllegalArgumentException("Random source is null");
}
synchronized (SOURCES) {
rng = SOURCES.computeIfAbsent(source, ThreadLocalRng::new);
}
}
return rng.get();
}
}