Java random number and ThreadLocalRandom

Posted by Awestruck on Tue, 21 Dec 2021 04:42:28 +0100

brief introduction

In JDK7, Java util. Concurrent contains a fairly convenient class of random number generation, ThreadLocalRandom, when an application expects to use random numbers in multiple threads or ForkJoinTasks. For concurrent access, use TheadLocalRandom instead of math Random () can reduce competition for better performance. In use, you only need to call ThreadLocalRandom Current (), and then call one of its methods to get a random number. Here is an example:

int randomNum = ThreadLocalRandom.current().nextInt(max);

Source code analysis

linear congruential method

Linear congruent method is also called "linear congruent Random number generator". One of the methods to generate [0,1] uniformly distributed Random numbers. Including mixed congruence method and multiplicative congruence method. It was proposed by Lemmer in 1951. The Random number generation algorithm in Java is implemented through it.

X[n + 1] = (a * X[n] + c) mod m

Among them,

  • M > 0, modulus data
  • 0 < = a < = m, multiplier
  • 0 < = C < = m, increment
  • 0 < = X0 < m, X0 start value

Random source code analysis

The following is the core source code of Random

private static final long multiplier = 0x5DEECE66DL;
private static final long addend = 0xBL;
private static final long mask = (1L << 48) - 1;

// Constructor initializes this seed
public Random(long seed) {
    if (getClass() == Random.class)
        this.seed = new AtomicLong(initialScramble(seed));
    else {
        // subclass might have overriden setSeed
        this.seed = new AtomicLong();
        setSeed(seed);
    }
}

protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        oldseed = seed.get();
        nextseed = (oldseed * multiplier + addend) & mask;
    // CAS ensures thread safety, but there may be performance problems in multithreading    
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}


// Get random number
public int nextInt(int bound) {
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);

    int r = next(31);
    int m = bound - 1;
    if ((bound & m) == 0)  // i.e., bound is a power of 2
        r = (int)((bound * (long)r) >> 31);
    else {
        for (int u = r;
             u - (r = u % bound) + m < 0;
             u = next(31))
            ;
    }
    return r;
}

From the source code, we can see that the core computing is

nextseed = (oldseed * multiplier + addend) & mask;

Then replace the fixed value to get the following formula

nextseed = (oldseed * 25214903917L + 11L) & 281474976710655L;

Seed in fact, we can also become a random seed and copy the formula again for easy reading

X[n + 1] = (a * X[n] + c) mod m

Where multiplier and append represent a and c in the formula respectively, but what does mask represent? Actually, x & [(1L < < 48) – 1] and x (mod 2^48) equivalent. Explain: x takes the remainder to the nth power of 2. Since the divisor is the nth power of 2, for example: 0001001000 is equivalent to moving the binary form of x to the right by N digits. At this time, the remainder is moved to the right of the decimal point, for example: 13 = 1101, 8 = 1000, 13 / 8 = 1.101. The 101 to the right of the decimal point is the remainder and converted into decimal is 5

Note: * * AtomicLong seed * * * * indicates that Random is a thread safe Random number tool class**

ThreadLocalRandom source code analysis

We can use threadlocalrandom Current() gets the instance.

public static ThreadLocalRandom current() {
        if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
            localInit();
        return instance;
}

If not initialized, localInit() will be called to initialize:

static final void localInit() {
    int p = probeGenerator.addAndGet(PROBE_INCREMENT);
    int probe = (p == 0) ? 1 : p; // skip 0
    long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
    Thread t = Thread.currentThread();
    UNSAFE.putLong(t, SEED, seed);
    UNSAFE.putInt(t, PROBE, probe);
}

Through the code, we can see that thread Currentthread() gets the current thread. Note the process of generating random numbers no longer depends on CAS to obtain shared objects. Let's finally look at the nextInt method:

public int nextInt(int bound) {
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);
    // Generate random number
    int r = mix32(nextSeed());
    int m = bound - 1;
    // Random number judgment
    if ((bound & m) == 0) // power of two
        // Remainder
        r &= m;
    else { // reject over-represented candidates
        // Retry until the interval requirements are met
        for (int u = r >>> 1;
             u + m - (r = u % bound) < 0;
             u = mix32(nextSeed()) >>> 1)
            ;
    }
    return r;
}

nextInt(int bound) has the same idea as nextInt, First call the mix32(nextSeed()) method to generate a random number (range of int type), and then judge the parameter n. if n is exactly the power of 2, the desired result can be obtained by direct shift; if it is not the power of 2, the remainder of n will be taken to make the result within the range of [0,n). In addition, the purpose of the for loop statement is to prevent the result from being negative.

Here we can see that it is mainly through mix32(nextSeed())

// Random number is generated according to the new seed. The random number algorithm is the same as random
private static int mix32(long z) {
    z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL;
    return (int)(((z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L) >>> 32);
}


// Generate a new seed and save it in thread Threadlocalrandomseed. GAMMA=0x9e3779b97f4a7c15L
final long nextSeed() {
    Thread t; long r; // read and update per-thread seed
    UNSAFE.putLong(t = Thread.currentThread(), SEED,
                   r = UNSAFE.getLong(t, SEED) + GAMMA);
    return r;
}

Use case

Random

The following is how the Random class generates Random numbers

public class RandomTest {
    static Random RANDOM = new Random();

    public static void main(String[] args) {
        final int max = 1000000;
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                int randomNum = RANDOM.nextInt(max);
                System.out.println("randomNum: " + randomNum);
            }).start();
        }
    }
}

ThreadLocalRandom

Here is a simple ThreadLocalRandom, as follows:

public class ThreadLocalRandomTest {

    public static void main(String[] args) {
        final int max = 1000000;
        for (int i = 0; i < 1000; i++) {
            new Thread(()-> {
                int randomNum = ThreadLocalRandom.current().nextInt(max);
                System.out.println("randomNum: " + randomNum);
            }).start();
        }
    }
}

// Output results
randomNum: 648582
randomNum: 76984
randomNum: 561085
randomNum: 120131
randomNum: 210919
randomNum: 546645
randomNum: 194225
randomNum: 930034
randomNum: 203882

Usage Summary

Avoid the random instance being used by multiple threads. Although sharing the instance is thread safe, it will cause performance degradation due to competing for the same seed. Note: random instances include Java util. An instance of random or math Random(). Positive example: after JDK7, you can directly use API ThreadLocalRandom. Before JDK7, you need to code to ensure that each thread holds a separate random instance.

reference material

  • The art of computer programming TACOP
  • Java development manual
  • https://www.cnblogs.com/shamo89/p/8052161.html
  • http://ifeve.com/tag/threadlocalrandom
  • https://www.cnblogs.com/softidea/p/5824240.html#3697214
  • https://baike.baidu.com/item/%E7%BA%BF%E6%80%A7%E5%90%8C%E4%BD%99%E6%B3%95/10528746?fr=aladdin
  • https://www.cnblogs.com/binarylei/p/10965556.html