java.util.Random和concurrent.ThreadLocalRandom對比
最近工作中遇到了一個需求,需要以一定的概率過濾掉一部分的流量,想想只能用Random了,因為是在多線程環境下,我還特意確認了下Random在多線程是否能正常運行,Random的實現也比較簡單,初始化的時候用當前的事件來初始化一個隨機數種子,然后每次取值的時候用這個種子與有些MagicNumber運算,并更新種子。最核心的就是這個next的函數,不管你是調用了nextDouble還是nextInt還是nextBoolean,Random底層都是調這個next(int bits)。

protected int next(int bits) { long oldseed, nextseed; AtomicLong seed = this.seed; do { oldseed = seed.get(); nextseed = (oldseed * multiplier + addend) & mask; } while (!seed.compareAndSet(oldseed, nextseed)); return (int)(nextseed >>> (48 - bits)); }
1
2
3
4
5
6
7
8
9
為了保證多線程下每次生成隨機數都是用的不同,next()得保證seed的更新是原子操作,所以用了AtomicLong的compareAndSet(),該方法底層調用了sum.misc.Unsafe的compareAndSwapLong(),也就是大家常聽到的CAS, 這是一個native方法,它能保證原子更新一個數。
既然Random滿足我的需求,又能在多線程下正常運行,所以我直接用了random,后來在codeReview中,同事提出用concurrent.ThreadLocalRandom來替代Random。我腦子里立馬冒出一個問題,既然random是線程安全的,為什么concurrent包里還要實現一個random。在oracle的jdk文檔里發現這樣一句話
use of ThreadLocalRandom rather than shared Random objects in concurrent programs will typically encounter much less overhead and contention. Use of ThreadLocalRandom is particularly appropriate when multiple tasks (for example, each a ForkJoinTask) use random numbers in parallel in thread pools.
大意就是用ThreadLocalRandom更適合用在多線程下,能大幅減少多線程并行下的性能開銷和資源爭搶。
既然文檔里說的牛逼,到底能有多少的性能提升?我做了一個簡單的測試。測試環境:24核 CPU, jdk8,每個隨機生成100000個double數,,分別測試不同線程數下rando和ThreadLocalRandom的運行時間,數據如下
ThreadNum,Random,ThreadLocalRandom 50,1192,575 100,4031,162 150,6068,223 200,8093,287 250,10049,248 300,12346,200 350,14429,212 400,16491,62 450,18475,96 500,11311,97 550,12421,90 600,13577,102 650,14718,111 700,15896,127 750,17101,129 800,17907,203 850,19261,226 900,21576,151 950,22206,147 1000,23418,174
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ThreadLocalRandom雖然也有波動,但基本上是平的,而random隨著線程數的增加一直在增加,在1000個線程時兩者居然有百倍的性能差距。不過這里有個讓人百思不得其解的現象,為什么random的耗時在500個線程的時候又掉下來,測試多次都是這個情況,可見并不是偶發現象。
我也在本人的筆記本上測了下,我筆記本雙核i7,ThreadLocalRandom和Random性能差距最高也有100倍,我發現我筆記本比公司服務器跑的快(數據如下)。。。。我也在一臺1核的阿里云ECS上測試了,按道理1核心的技術上,即便是多線程起始也是串行執行的,但ThreadLocalRandom和Random在1000個線程的情況下也有6倍的性能差距。
既然ThreadLocalRandom在多線程下表現這么牛逼,它究竟是如何做到的?我們來看下源碼,它的核心代碼是這個
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; }
1
2
3
4
5
6
起始ThreadLocalRandom是對每個線程都設置了單獨的隨機數種子,這樣就不會發生多線程同時更新一個數時產生的資源爭搶了,用空間換時間。
最后附上Random和ThreadLocalRandom的性能測試代碼
import Java.util.Random; import Java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; public class RandomTest { private static Random random = new Random(); private static final int N = 100000; // Random from java.util.concurrent. private static class TLRandom implements Runnable { @Override public void run() { double x = 0; for (int i = 0; i < N; i++) { x += ThreadLocalRandom.current().nextDouble(); } } } // Random from java.util private static class URandom implements Runnable { @Override public void run() { double x = 0; for (int i = 0; i < N; i++) { x += random.nextDouble(); } } } public static void main(String[] args) { System.out.println("threadNum,Random,ThreadLocalRandom"); for (int threadNum = 50; threadNum <= 2000; threadNum += 50) { ExecutorService poolR = Executors.newFixedThreadPool(threadNum); long RStartTime = System.currentTimeMillis(); for (int i = 0; i < threadNum; i++) { poolR.execute(new URandom()); } try { poolR.shutdown(); poolR.awaitTermination(100, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } String str = "" + threadNum +"," + (System.currentTimeMillis() - RStartTime)+","; ExecutorService poolTLR = Executors.newFixedThreadPool(threadNum); long TLRStartTime = System.currentTimeMillis(); for (int i = 0; i < threadNum; i++) { poolTLR.execute(new TLRandom()); } try { poolTLR.shutdown(); poolTLR.awaitTermination(100, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(str + (System.currentTimeMillis() - TLRStartTime)); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
Java 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。