头图

ThreadLocalRandom class resolution

foreword

The ThreadLocalRandom class is a package. It mainly solves the shortcomings of the Random class under multi-threading.

This article mainly explains why the ThreadLocalRandom class is needed and the implementation principle of this class.

Random class and its limitations

First let's take a look at the Random class. Before JDK7 to now, the java.util.Random class is a widely used random number generation tool class, and the random number generation of java.lang.Math is also an instance of the java.util.Random class used, let's take a look How to use the Random class.

public static void main(String[] args) {
        //1. 创建一个默认种子随机数生成器
        Random random = new Random();
        //2. 输出10个在0-5之间的随机数(包含0,不包含5)
        for (int i = 0; i < 10; i++) {
            System.out.print(random.nextInt(5)); //3421123432
        }
}

The generation of random numbers requires a default seed of , which is actually a long type 061d6f5ec14eee, which can be specified through the constructor when creating the Random class object. If not specified, a default value will be generated inside the default constructor.

The seed number is just the starting number of the random algorithm, and has nothing to do with the interval of the generated random number.
public Random() {
        this(seedUniquifier() ^ System.nanoTime());
}
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);
        }
}

With the default seed, how does Random generate random numbers? Let's take a look at the nextInt() method.

public int nextInt(int bound) {
              //3. 参数检查
        if (bound <= 0)
            throw new IllegalArgumentException(BadBound);
                //4. 根据老的种子生成新的种子
        int r = next(31);
              //5. 根据新的种子计算随机数
        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;
}

It can be seen that the generation of new random numbers requires two steps:

  • Generate new seeds from old seeds
  • Then calculate a new random number based on the new seed

The step 4 can be abstracted as seed = f(seed) , such as seed = f(seed) = a*seed+b ;

Step 5 can be abstracted as g(seed,bound) , such as g(seed,bound) = (int) ((bound * (long) seed) >> 31) ;

In the case of a single thread, each call to nextInt() calculates a new seed based on the old seed, which ensures the randomness of random numbers. But under multi-threading, multiple threads may take the same old seed to execute step 4 to calculate the new seed, which will cause the new seed of multiple threads to be the same, and since the algorithm of 5 So it will cause generate the same random value .

Therefore, step 4 must ensure atomicity , that is to say, when multiple threads calculate new seeds based on the same old seed, after the new seed of the first thread is calculated, the second thread has to discard its old seed, Instead use the first thread's new seed to compute its own new seed, and so on.

atomic variable AtomicLong is used in Random to achieve this effect. when creating the Random object is saved to the seed atomic variable .

Next, take a look at the next() method:

protected int next(int bits) {
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
            //6. 获取当前变量种子
            oldseed = seed.get();
            //7. 根据当前种子变量计算新的种子
            nextseed = (oldseed * multiplier + addend) & mask;
          //8. 使用CAS操作,它使用新的种子去更新老的种子,失败的线程会通过循环重新获取更新后的种子作为当前种子去计算老的种子
        } while (!seed.compareAndSet(oldseed, nextseed));
        return (int)(nextseed >>> (48 - bits));
}

summarizes : Although only one thread will succeed at the same time under multi-threading, it will cause a large number of threads to perform spin operations, which will reduce concurrency performance, so ThreadLocalRandom is used.

ThreadLocalRandom

In order to make up for the efficiency problem of Random under multi-threading, the ThreadLocalRandom class is added to the JUC package. The following demonstrates how to use the ThreadLocalRandom class:

public static void main(String[] args) {
        //1. 获取一个随机数生成器
        ThreadLocalRandom random = ThreadLocalRandom.current();
        //2. 输出10个在0-5(包含0,不包含5)之间的随机数
        for (int i = 0; i < 10; ++i) {
            System.out.print(random.nextInt(5));
        }

}

The first step which calls ThreadLocalRandom.current() to get current thread random numbers.

In fact, the implementation of ThreadLocalRandom is similar to that of ThreadLocal, by allowing each thread to copy a variable, so that each thread actually operates a copy in the local memory, avoiding the synchronization of shared variables.

The disadvantage of Random is that multiple threads will use the same atomic seed variable, resulting in races for atomic variable updates.

In ThreadLocalRandom, each thread maintains a seed variable. When each thread generates a random number, it calculates a new seed according to the old seed in the current thread, and uses the new seed to update the old seed, and then according to the new seed The seed to calculate the random number, so that there will be no competition problem.

ThreadLocalRandom

Source code analysis

First, let's take a look at the class diagram structure of ThreadLocalRandom.

类图结构

It can be seen from the figure that ThreadLocalRandom inherits the Random class and rewrites the nextInt() method. The ThreadLocalRandom class does not use the atomic seed variable of Random.

There is no specific seed stored in ThreadLocalRandom, and the specific seeds are placed in the ThreadLocalRandomSeed variable of the specific calling thread.

ThreadLocalRandom is similar to the ThreadLocal class and is a utility class. When a thread calls the current() method of ThreadLocalRandom, ThreadLocalRandom is responsible for initializing the threadLocalRandomSeed variable of the calling thread and initializing the seed.

When calling the nextInt() method of ThreadLocalRandom, it actually obtains the ThreadLocalRandomSeed variable of the current thread as the current seed to calculate the new seed, then updates the new seed to ThreadLocalRandomSeed, and then calculates the random number according to the new seed.

It should be noted that the threadLocalRandomSeed variable is an ordinary long type variable in the Thread class, not an atomic type variable.

@sun.misc.Contended("tlr")
long threadLocalRandomSeed;

Because this variable is thread-level, there is no need to use an atomic type variable at all.

The seeder and probeGenerator in ThreadLocalRandom are two atomic variables. The purpose of declaring probeGenerator and seeder as atomic variables is to give them different initial seed values in the case of multi-threading, so that it will not cause each thread to generate The random number sequence is the same, and probeGenerator and seeder are only used when initializing the calling thread's seed and probe variables (used for scattered calculation of array index subscripts), and each thread will only use it once.

Further ThreadLocalRandom variable instance is an instance of this variable is static, instance multiple threads is the same, but because specific seed thread is present inside , so example ThreadlocalRandom which contains only the thread-independent Generic algorithm, so it's thread-safe .

Let's take a look at the main code logic of the ThreadLocalRandom class:

1. Unsafe mechanism

    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long SEED;
    private static final long PROBE;
    private static final long SECONDARY;
    static {
        try {
              //获取实例
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            //获取Thread类中的threadLocalRandomSeed变量在Thread实例里面的偏移量
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            //获取Thread类中的threadLocalRandomProbe变量在Thread实例里面的偏移量
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            //获取Thread类中的threadLocalRandomSecondarySeed变量在Thread实例里面的偏移量
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

2. ThreadLocalRandom current() method

This method obtains the ThreadLocalRandom instance and initializes the threadLocalRandomSeed and threadLocalRandomProbe variables in the calling thread.

static final ThreadLocalRandom instance = new ThreadLocalRandom();
public static ThreadLocalRandom current() {
        if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
            localInit();
        return instance;
}
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);
}

current() current thread is 0 (by default, this variable of the thread is 0), it means that the current thread is calling the localInit() method needs to be called to calculate the initialization of the current thread seed variable.

In order to delay initialization here, the seed variable in the Thread class is not initialized when the random number function is not needed.

In localInit() , first calculate the initialization value of threadLocalRandomProbe in the current thread according to probeGenerator, then calculate the initialization seed of the current thread according to the seeder, and then set these two variables to the current thread.

Returns an instance of ThreadLocalRandom at the end of current. It should be noted that the is a static method, and multiple threads return the same ThreadLocalRandom instance .

3.int nextInt(int bound) method

Calculates the next random number for the current thread.

public int nextInt(int bound) {
        //参数校验
        if (bound <= 0)
            throw new IllegalArgumentException(BadBound);
        //根据当前线程中的种子计算新种子
        int r = mix32(nextSeed());
        //根据新种子计算随机数
        int m = bound - 1;
        if ((bound & m) == 0) // power of two
            r &= m;
        else { // reject over-represented candidates
            for (int u = r >>> 1;
                 u + m - (r = u % bound) < 0;
                 u = mix32(nextSeed()) >>> 1)
                ;
        }
        return r;
}

The above logic is similar to Random, the focus is on the nextSeed() method, which is mainly to obtain and update the respective seed variables and generate random numbers.

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;
}

In this code, first use the variable r = UNSAFE.getLong(t,SEED) to obtain the value of the threadLocalRandomSeed variable in the current thread, then add the GAMMA as a new seed based on the seed, and then use the putLong method of UNSAFE to put the new seed into the current thread threadLocalRandomSeed variable.

4. long initialSeed() method

private static final AtomicLong seeder = new AtomicLong(initialSeed());

    private static long initialSeed() {
        String sec = VM.getSavedProperty("java.util.secureRandomSeed");
        if (Boolean.parseBoolean(sec)) {
            byte[] seedBytes = java.security.SecureRandom.getSeed(8);
            long s = (long)(seedBytes[0]) & 0xffL;
            for (int i = 1; i < 8; ++i)
                s = (s << 8) | ((long)(seedBytes[i]) & 0xffL);
            return s;
        }
        return (mix64(System.currentTimeMillis()) ^
                mix64(System.nanoTime()));
}

When initializing the atomic variable seeder corresponding to the initial value of the seed variable, the initialSeed() method is called. First, determine java.util.secureRandomSeed the system attribute value of 061d6f5ec154cc is true to determine whether to use a high-security seed. If it is true, use java.security.SecureRandom.getSeed(8) obtain a high-security seed. , if it is false, the initialization seed is obtained according to the current timestamp, that is to say, the use of seeds with high security cannot be predicted, and the ones generated by Random and ThreadLocalRandom are called "pseudo-random numbers" because they are predictable .

Summarize

ThreadLocalRandom uses the principle of ThreadLocal to allow each thread to hold a local seed variable, which will only be initialized when random numbers are used. When calculating a new seed under multi-threading, it is updated according to the seed variable maintained in its own thread, thus avoiding competition.


神秘杰克
765 声望382 粉丝

Be a good developer.


引用和评论

0 条评论