Skip to content

Commit

Permalink
Refactor RandomUtil
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiolimace committed Jun 9, 2024
1 parent ece6a06 commit e561d33
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public interface NodeIdFunction extends LongSupplier {
* @return a number in the range 0 to 2^48-1.
*/
public static long getRandom() {
return toExpectedRange(RandomUtil.nextLong());
return toExpectedRange(RandomUtil.newSecureRandom().nextLong());
}

/**
Expand All @@ -57,7 +57,7 @@ public static long getRandom() {
* @return a number in the range 0 to 2^48-1.
*/
public static long getMulticastRandom() {
return toMulticast(RandomUtil.nextLong());
return toMulticast(getRandom());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,91 +24,19 @@

package com.github.f4b6a3.uuid.factory.function.impl;

import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

import com.github.f4b6a3.uuid.factory.function.RandomFunction;
import com.github.f4b6a3.uuid.util.internal.RandomUtil;

/**
* Function that returns an array of bytes with the given length.
* <p>
* The current implementation uses a pool {@link SecureRandom}.
* <p>
* The pool size depends on the number of processors available, up to a maximum
* of 32. The minimum is 4.
* <p>
* The pool items are deleted very often to avoid holding them for too long.
* They are also deleted to avoid holding more instances than threads running.
* <p>
* The PRNG algorithm can be specified by system property or environment
* variable. See {@link RandomUtil#newSecureRandom()}.
*
* @see RandomFunction
* @see RandomUtil#newSecureRandom()
* @see RandomUtil
*/
public final class DefaultRandomFunction implements RandomFunction {

private static final int POOL_SIZE = processors();
private static final Random[] POOL = new Random[POOL_SIZE];
private static final ReentrantLock lock = new ReentrantLock();

@Override
public byte[] apply(final int length) {

final byte[] bytes = new byte[length];
current().nextBytes(bytes);

// every now and then
if (bytes.length > 0 && bytes[0x00] == 0) {
// delete a random item from the pool
delete((new Random()).nextInt(POOL_SIZE));
}

return bytes;
}

private static Random current() {

// calculate the pool index given the current thread ID
final int index = (int) Thread.currentThread().getId() % POOL_SIZE;

lock.lock();
try {
// lazy loading instance
if (POOL[index] == null) {
POOL[index] = RandomUtil.newSecureRandom();
}
return POOL[index];
} finally {
lock.unlock();
}
}

private static void delete(int index) {
lock.lock();
try {
POOL[index] = null;
} finally {
lock.unlock();
}
}

private static int processors() {

final int min = 4;
final int max = 32;

// get the number of processors from the runtime
final int processors = Runtime.getRuntime().availableProcessors();

if (processors < min) {
return min;
} else if (processors > max) {
return max;
}

return processors;
return RandomUtil.nextBytes(length);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
import static com.github.f4b6a3.uuid.util.UuidTime.TICKS_PER_MILLI;

import java.time.Clock;
import java.util.SplittableRandom;

import com.github.f4b6a3.uuid.factory.function.TimeFunction;
import com.github.f4b6a3.uuid.util.internal.RandomUtil;

/**
* Function that returns a number of 100-nanoseconds since 1970-01-01 (Unix
Expand All @@ -46,7 +46,7 @@ public final class DefaultTimeFunction implements TimeFunction {
private long lastTime = -1;

// start the counter with a random number between 0 and 9,999
private long counter = Math.abs(RandomUtil.nextLong() % TICKS_PER_MILLI);
private long counter = Math.abs(new SplittableRandom().nextLong()) % TICKS_PER_MILLI;
// start the counter limit with a number between 10,000 and 19,999
private long counterMax = counter + TICKS_PER_MILLI;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package com.github.f4b6a3.uuid.factory.function.impl;

import com.github.f4b6a3.uuid.factory.function.NodeIdFunction;
import com.github.f4b6a3.uuid.util.internal.RandomUtil;

/**
* Function that returns a new random multicast node identifier.
Expand All @@ -37,6 +38,6 @@ public final class RandomNodeIdFunction implements NodeIdFunction {

@Override
public long getAsLong() {
return NodeIdFunction.getMulticastRandom();
return NodeIdFunction.toMulticast(RandomUtil.nextLong());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
import static com.github.f4b6a3.uuid.util.UuidTime.TICKS_PER_MILLI;

import java.time.Clock;
import java.util.SplittableRandom;

import com.github.f4b6a3.uuid.factory.function.TimeFunction;
import com.github.f4b6a3.uuid.util.internal.RandomUtil;

/**
* Function that returns a number of 100-nanoseconds since 1970-01-01 (Unix
Expand All @@ -55,7 +55,7 @@ public final class WindowsTimeFunction implements TimeFunction {
private static final long TICKS_PER_GRANULARITY = TICKS_PER_MILLI * GRANULARITY;

// start the counter with a random number between 0 and 159,999
private long counter = Math.abs(RandomUtil.nextLong() % TICKS_PER_GRANULARITY);
private long counter = Math.abs(new SplittableRandom().nextLong()) % TICKS_PER_GRANULARITY;
// start the counter limit with a number between 160,000 and 319,999
private long counterMax = counter + TICKS_PER_GRANULARITY;

Expand Down
107 changes: 93 additions & 14 deletions src/main/java/com/github/f4b6a3/uuid/util/internal/RandomUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,45 @@

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

/**
* Utility class that wraps a shared {@link SecureRandom} and provides new
* instances of {@link SecureRandom}.
* Utility class that provides random generator services.
* <p>
* The current implementation uses a pool {@link SecureRandom}.
* <p>
* The pool size depends on the number of processors available, up to a maximum
* of 32. The minimum is 4.
* <p>
* The pool items are deleted very often to avoid holding them for too long.
* They are also deleted to avoid holding more instances than threads running.
* <p>
* The PRNG algorithm can be specified by system property or environment
* variable. See {@link RandomUtil#newSecureRandom()}.
*/
public final class RandomUtil {

/**
* A globally shared {@link SecureRandom} instance.
*/
protected static final SecureRandom SHARED_RANDOM = newSecureRandom();

private RandomUtil() {
}

/**
* Returns a random 32-bit number.
* Returns a random 64-bit number.
*
* @return a number
*/
public static int nextInt() {
return SHARED_RANDOM.nextInt();
public static long nextLong() {
return SecureRandomPool.nextLong();
}

/**
* Returns a random 64-bit number.
* Returns an array of random bytes.
*
* @return a number
* @param length the array length
* @return a byte array
*/
public static long nextLong() {
return SHARED_RANDOM.nextLong();
public static byte[] nextBytes(final int length) {
return SecureRandomPool.nextBytes(length);
}

/**
Expand Down Expand Up @@ -110,4 +118,75 @@ public static SecureRandom newSecureRandom() {
}
return new SecureRandom();
}

private static class SecureRandomPool {

private static final int POOL_SIZE = processors();
private static final Random[] POOL = new Random[POOL_SIZE];
private static final ReentrantLock lock = new ReentrantLock();

private SecureRandomPool() {
}

public static long nextLong() {
return ByteUtil.toNumber(nextBytes(Long.BYTES));
}

public static byte[] nextBytes(final int length) {

final byte[] bytes = new byte[length];
current().nextBytes(bytes);

// every now and then
if (bytes.length > 0 && bytes[0x00] == 0) {
// delete a random item from the pool
delete((new Random()).nextInt(POOL_SIZE));
}

return bytes;
}

private static Random current() {

// calculate the pool index given the current thread ID
final int index = (int) Thread.currentThread().getId() % POOL_SIZE;

lock.lock();
try {
// lazy loading instance
if (POOL[index] == null) {
POOL[index] = RandomUtil.newSecureRandom();
}
return POOL[index];
} finally {
lock.unlock();
}
}

private static void delete(int index) {
lock.lock();
try {
POOL[index] = null;
} finally {
lock.unlock();
}
}

private static int processors() {

final int min = 4;
final int max = 32;

// get the number of processors from the runtime
final int processors = Runtime.getRuntime().availableProcessors();

if (processors < min) {
return min;
} else if (processors > max) {
return max;
}

return processors;
}
}
}

0 comments on commit e561d33

Please sign in to comment.