爲何要使用ThreadLocalRandom代替Random生成隨機數

 

java裏有僞隨機型和安全型兩種隨機數生成器,僞隨機生成器根據特定公式將seed轉換成新的僞隨機數據的一部分,安全隨機生成器在底層依賴到操做系統提供的隨機事件來生成數據。java

安全隨機生成器算法

  • 須要生成加密性強的隨機數據的時候才用它
  • 生成速度慢
  • 若是須要生成大量的隨機數據,可能會產生阻塞須要等待外部中斷事件

而僞隨機生成器,只依賴於「seed」的初始值,若是給生成算法提供相同的seed,能夠獲得同樣的僞隨機序列。通常狀況下,因爲它是計算密集型的(不依賴於任何IO設備),所以生成速度更快。如下是僞隨機生成器的進化史。數組

java.util.Random 
自1.0就已經存在,是一個線程安全類,理論上能夠經過它同時在多個線程中得到互不相同的隨機數,這樣的線程安全是經過AtomicLong實現的。 
Random使用AtomicLong CAS(compare and set)操做來更新它的seed,儘管在不少非阻塞式算法中使用了非阻塞式原語,CAS在資源高度競爭時的表現依然糟糕,後面的測試結果中能夠看到它的糟糕表現。緩存

java.util.concurrent.ThreadLocalRandom 
1.7增長該類,企圖將它和Random結合以克服全部的性能問題,該類繼承自Random。安全

ThreadLocalRandom的主要實現細節:markdown

  • 使用一個普通的long而不是使用Random中的AtomicLong做爲seed
  • 不能本身建立ThreadLocalRandom實例,由於它的構造函數是私有的,可使用它的靜態工廠ThreadLocalRandom.current()
  • 它是CPU緩存感知式的,使用8個long虛擬域來填充64位L1高速緩存行

測試dom

下面進行5種測試:ide

  1. 一個單獨的Random被N個線程共享
  2. ThreadLocal<Random>
  3. ThreadLocalRandom
  4. Random[], 其中每一個線程N使用一個數組下標爲N的Random
  5. Random[], 其中每一個線程N使用一個數組下標爲N * 2的Random

全部的測試都使用封裝在RandomTask類裏的方法,每一個方案都說明了如何使用隨機生成器。函數

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;

public class Test_Random {

    private static final long COUNT = 10000000;
    private static final int THREADS = 2;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println("Shared Random");
        testRandom(THREADS, COUNT);
        /*System.out.println("ThreadLocal<Random>");
        testThreadLocal_Random(THREADS, COUNT);
        System.out.println("ThreadLocalRandom");
        testThreadLocalRandom(THREADS, COUNT);
        System.out.println("Shared Random[] with no padding");
        testRandomArray(THREADS, COUNT, 1);
        System.out.println("Shared Random[] with padding");
        testRandomArray(THREADS, COUNT, 2);*/
    }

    private static class RandomTask implements Runnable {
        private final Random rnd;
        protected final int id;
        private final long cnt;
        private final CountDownLatch latch;

        private RandomTask(Random rnd, int id, long cnt,
                CountDownLatch latch) {
            super();
            this.rnd = rnd;
            this.id = id;
            this.cnt = cnt;
            this.latch = latch;
        }

        protected Random getRandom() {
            return rnd;
        }

        @Override
        public void run() {
            try {
                final Random r = getRandom();
                latch.countDown();
                latch.await();
                final long start = System.currentTimeMillis();
                int sum = 0;
                for (long j = 0; j < cnt; j++) {
                    sum += r.nextInt();
                }
                final long time = System.currentTimeMillis() - start;
                System.out.println("Thread #" + id + " Time = " + time / 1000.0 + " sec, sum = " + sum);
            } catch (InterruptedException e) {}
        }
    }

    private static void testRandom(final int threads, final long cnt) {
        final CountDownLatch latch = new CountDownLatch(threads);
        final Random r = new Random(100);
        for (int i = 0; i < threads; ++i) {
            final Thread thread = new Thread(new RandomTask(r, i, cnt, latch));
            thread.start();
        }
    }

    private static void testRandomArray(final int threads, final long cnt, final int padding) {
        final CountDownLatch latch = new CountDownLatch(threads);
        final Random[] rnd = new Random[threads * padding];
        for (int i = 0; i < threads * padding; ++i) {
            rnd[i] = new Random(100);
        }
        for (int i = 0; i < threads; ++i) {
            final Thread thread = new Thread(new RandomTask(rnd[i * padding], i, cnt, latch));
            thread.start();
        }
    }

    private static void testThreadLocalRandom(final int threads, final long cnt) {
        final CountDownLatch latch = new CountDownLatch(threads);
        for (int i = 0; i < threads; ++i) {
            final Thread thread = new Thread(new RandomTask(null, i, cnt, latch) {
                @Override
                protected Random getRandom() {
                    // TODO Auto-generated method stub
                    return ThreadLocalRandom.current();
                }
            });
            thread.start();
        }
    }

    private static void testThreadLocal_Random(final int threads, final long cnt) {
        final CountDownLatch latch = new CountDownLatch(threads);
        final ThreadLocal<Random> rnd = new ThreadLocal<Random>() {

            @Override
            protected Random initialValue() {
                // TODO Auto-generated method stub
                return new Random(100);
            }

        };
        for (int i = 0; i < threads; ++i) {
            final Thread thread = new Thread(new RandomTask(null, i, cnt, latch) {

                @Override
                protected Random getRandom() {
                    // TODO Auto-generated method stub
                    return rnd.get();
                }

            });
            thread.start();
        }
    }
}

總結:post

  • 任何狀況下都不要在多個線程間共享一個Random實例,而該把它放入ThreadLocal之中
  • java7在全部情形下都更推薦使用ThreadLocalRandom,它向下兼容已有的代碼且運營成本更低
相關文章
相關標籤/搜索