1. 程式人生 > >為什麽要使用ThreadLocalRandom代替Random生成隨機數

為什麽要使用ThreadLocalRandom代替Random生成隨機數

方案 current val todo popu 轉換成 int() read nbsp

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

安全隨機生成器

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

而偽隨機生成器,只依賴於“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的主要實現細節:

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

測試

下面進行5種測試:

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

總結:

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

為什麽要使用ThreadLocalRandom代替Random生成隨機數