日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java中随机数的原理,以及使用时的注意点

發布時間:2023/12/3 java 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java中随机数的原理,以及使用时的注意点 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載自? ?Java中隨機數的原理,以及使用時的注意點

1 前言

一提到 Java 中的隨機數,很多人就會想到 Random,當出現生成隨機數這樣需求時,大多數人都會選擇使用 Random 來生成隨機數。Random 類是線程安全的,但其內部使用 CAS 來保證線程安全性,在多線程并發的時候它的表現是存在優化空間的。在 JDK1.7 之后,Java 提供了更好的解決方案 ThreadLocalRandom,接下來,我們一起探討下這幾個隨機數生成器的實現到底有何不同。

2 Random

Random 這個類是 JDK 提供的用來生成隨機數的一個類,這個類并不是真正的隨機,而是偽隨機,偽隨機的意思是生成的隨機數其實是有一定規律的,而這個規律出現的周期隨著偽隨機算法的優劣而不同,一般來說周期比較長,但是可以預測。通過下面的代碼我們可以對 Random 進行簡單的使用:?

public?class?RandomDemo?{public?static?void?main(String[]?args)?{Random?rand?=?new?Random();System.out.println(rand.nextInt());System.out.println(rand.nextInt(10));} }

Random原理

Random 中的方法比較多,這里就針對比較常見的 nextInt() 和 nextInt(int bound) 方法進行分析,前者會計算出 int 范圍內的隨機數,后者如果我們傳入 10,那么他會返回 [0,10) 之間的 int 類型的隨機數,左閉右開。我們首先看一下 Random() 的構造方法:?

public?Random()?{//?默認構造方法傳入的seed值,?//?這個值由種子算法得到的一個唯一值與納秒值通過位運算得到//?盡可能做到了和另一個構造方法的seed值的不同this(seedUniquifier()?^?System.nanoTime()); }public?Random(long?seed)?{if?(getClass()?==?Random.class)this.seed?=?new?AtomicLong(initialScramble(seed));else?{//?subclass?might?have?overriden?setSeedthis.seed?=?new?AtomicLong();setSeed(seed);} }private?static?long?seedUniquifier()?{//?L'Ecuyer,?"Tables?of?Linear?Congruential?Generators?of//?Different?Sizes?and?Good?Lattice?Structure",?1999for?(;;)?{long?current?=?seedUniquifier.get();long?next?=?current?*?181783497276652981L;if?(seedUniquifier.compareAndSet(current,?next))return?next;} }private?static?final?AtomicLong?seedUniquifier?=?new?AtomicLong(8682522807148012L);

?

可以發現在構造方法當中,根據當前時間(納秒)的種子生成了一個 AtomicLong 類型的 seed,這也是我們后續的關鍵所在。

?

nextInt()

nextInt() 的代碼如下所示:

public?int?nextInt()?{return?next(32); }

?

這個里面直接調用的是 next() 方法,傳入的 32,代指的是 Int 類型的位數。

?

protected?int?next(int?bits)?{long?oldseed,?nextseed;AtomicLong?seed?=?this.seed;do?{oldseed?=?seed.get();// 根據一定的規則生成nextseednextseed?=?(oldseed?*?multiplier?+?addend)?&?mask;// 更新oldseed的值,通過cas保證線程安全}?while?(!seed.compareAndSet(oldseed,?nextseed));// 返回前還需要位運算return?(int)(nextseed?>>>?(48?-?bits)); }

?

這里會根據 seed 當前的值,通過一定的規則((oldseed * multiplier + addend) & mask; 即偽隨機算法)算出下一個 seed,然后進行 CAS,如果 CAS 失敗則繼續循環上面的操作。最后根據我們需要的 bit 位數來進行返回。核心便是 CAS 算法。

nextInt(int bound)

nextInt(int bound) 的代碼如下所示:

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?2r?=?(int)((bound?*?(long)r)?>>?31);else?{for?(int?u?=?r;u?-?(r?=?u?%?bound)?+?m?<?0;u?=?next(31));}return?r; }

?

這個流程比 nextInt() 多了幾步,具體步驟如下:

  • 首先獲取 31 位的隨機數,注意這里是 31 位,和上面 32 位不同,因為在 nextInt() 方法中可以獲取到隨機數可能是負數,而 nextInt(int bound) 規定只能獲取到 [0,bound) 之前的隨機數,也就意味著必須是正數,預留一位符號位,所以只獲取了31位。(不要想著使用取絕對值這樣操作,會導致性能下降)
  • 然后進行取 bound 操作。
  • 如果 bound 是2的冪次方,可以直接將第一步獲取的值乘以 bound 然后右移31位,解釋一下:如果 bound 是4,那么乘以4其實就是左移2位,其實就是變成了33位,再右移31位的話,就又會變成2位,最后,2位 int 的范圍其實就是 [0,4) 了。
  • 如果不是 2 的冪,通過模運算進行處理。
  • ?

    并發瓶頸

    一般而言,CAS 相比加鎖有一定的優勢,但并不一定意味著高效,并發很大的情況下回造成CPU飆高。一個立刻被想到的解決方案是每次使用 Random 時都去 new 一個新的線程私有化的 Random 對象,或者使用 ThreadLocal 來維護線程私有化對象,但除此之外還存在更高效的方案,下面便來介紹本文的主角 ThreadLocalRandom。

    3 ThreadLocalRandom

    在 JDK1.7 之后提供了新的類 ThreadLocalRandom 用來在并發場景下代替 Random。使用方法比較簡單:

    public?class?RandomTest?{public?static?void?main(String[]?args)?{System.out.println(ThreadLocalRandom.current().nextInt());System.out.println(ThreadLocalRandom.current().nextInt());System.out.println(ThreadLocalRandom.current().nextInt(10));} }

    在 current 方法中有:

    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?0long?seed?=?mix64(seeder.getAndAdd(SEEDER_INCREMENT));Thread?t?=?Thread.currentThread();UNSAFE.putLong(t,?SEED,?seed);UNSAFE.putInt(t,?PROBE,?probe); }

    ?

    可以看見如果沒有初始化會對其進行初始化,而這里我們的 seed 不再是一個全局變量,而是每個線程私有的,在我們的Thread中有三個變量:?

    • threadLocalRandomSeed:ThreadLocalRandom 使用它來控制隨機數種子。
    • threadLocalRandomProbe:ThreadLocalRandom 使用它來控制初始化。
    • threadLocalRandomSecondarySeed:二級種子。

    可以看見所有的變量都加了 @sun.misc.Contended 這個注解,用來處理偽共享問題。

    在 nextInt() 方法當中代碼如下:

    public?int?nextInt()?{return?mix32(nextSeed()); }final?long?nextSeed()?{Thread?t;?long?r;?//?read?and?update?per-thread?seedUNSAFE.putLong(t?=?Thread.currentThread(),?SEED,r?=?UNSAFE.getLong(t,?SEED)?+?GAMMA);return?r; }

    我們的關鍵代碼如下:

  • UNSAFE.putLong(t = Thread.currentThread(), SEED,r=UNSAFE.getLong(t, SEED) + GAMMA);
  • 可以看見由于我們每個線程各自都維護了種子,這個時候并不需要 CAS,直接進行 put,在這里利用線程之間隔離,減少了并發沖突;相比較 ThreadLocal<Random>,ThreadLocalRandom 不僅僅減少了對象維護的成本,其內部實現也更輕量級。所以 ThreadLocalRandom 性能很高。

    4 性能測試

    除了文章中詳細介紹的 Random,ThreadLocalRandom,我還將 netty4 實現的 ThreadLocalRandom,以及 ThreadLocal<Random> 作為參考對象,一起參與 JMH 測評。

    @BenchmarkMode({Mode.AverageTime}) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations = 3, time = 5) @Measurement(iterations = 3, time = 5) @Threads(50) @Fork(1) @State(Scope.Benchmark) public class RandomBenchmark {Random random = new Random();ThreadLocal<Random> threadLocalRandomHolder = ThreadLocal.withInitial(Random::new);@Benchmarkpublic int random() {return random.nextInt();}@Benchmarkpublic int threadLocalRandom() {return ThreadLocalRandom.current().nextInt();}@Benchmarkpublic int threadLocalRandomHolder() {return threadLocalRandomHolder.get().nextInt();}@Benchmarkpublic int nettyThreadLocalRandom() {return io.netty.util.internal.ThreadLocalRandom.current().nextInt();}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(RandomBenchmark.class.getSimpleName()).build();new Runner(opt).run();}}

    測評結果如下:

    Benchmark ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Mode ?Cnt ? ? Score ? ? Error ?Units

    RandomBenchmark.nettyThreadLocalRandom ? avgt ? ?3 ? 192.202 ± 295.897 ?ns/op

    RandomBenchmark.random ? ? ? ? ? ? ? ? ? avgt ? ?3 ?3197.620 ± 380.981 ?ns/op

    RandomBenchmark.threadLocalRandom ? ? ? ?avgt ? ?3 ? ?90.731 ± ?39.098 ?ns/op

    RandomBenchmark.threadLocalRandomHolder ?avgt ? ?3 ? 229.502 ± 267.144 ?ns/op

    從上圖可以發現,JDK1.7 的 ThreadLocalRandom 取得了最好的成績,僅僅需要 90 ns 就可以生成一次隨機數,netty 實現的 ThreadLocalRandom 以及使用 ThreadLocal 維護 Random 的方式差距不是很大,位列 2、3 位,共享的 Random 變量則效果最差。

    可見,在并發場景下,ThreadLocalRandom 可以明顯的提升性能。

    5 注意點

    注意,ThreadLocalRandom 切記不要調用 current 方法之后,作為共享變量使用

    public class WrongCase {ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current()public int concurrentNextInt(){return threadLocalRandom.nextInt();}}

    這是因為 ThreadLocalRandom.current() 會使用初始化它的線程來填充隨機種子,這會帶來導致多個線程使用相同的 seed。

    public?class?BadCase?{public?static?void?main(String[]?args)?{ThreadLocalRandom?rand?=?ThreadLocalRandom.current();for?(int?i?=?0;?i?<?10;?i++)?{new?Thread(()?->?{System.out.println(rand.nextInt());}).start();}} }

    輸出相同的隨機數:

    -1667209487

    -1667209487

    -1667209487

    -1667209487

    -1667209487

    -1667209487

    -1667209487

    -1667209487

    -1667209487

    -1667209487

    確保不同線程獲取不同的 seed,最簡單的方式便是每次調用都是使用 current():

    public?class?GoodCase?{public?static?void?main(String[]?args)?{for?(int?i?=?0;?i?<?3;?i++)?{new?Thread(()?->?{System.out.println(ThreadLocalRandom.current().nextInt());System.out.println(ThreadLocalRandom.current().nextInt());System.out.println(ThreadLocalRandom.current().nextInt());}).start();}} }

    彩蛋1

    梁飛博客中一句話常常在我腦海中縈繞:魔鬼在細節中。優秀的代碼都是一個個小細節堆砌出來,今天介紹的 ThreadLocalRandom 也不例外。

    ?

    在 incubator-dubbo-2.7.0 中,隨機負載均衡器的一個小改動便是將 Random 替換為了 ThreadLocalRandom,用于優化并發性能。

    彩蛋2

    ThreadLocalRandom 的 nextInt(int bound) 方法中,當 bound 不為 2 的冪次方時,進入 else 分支,使用了一個循環來修改 r 的值,我認為這可能不必要,你覺得呢?

    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 twor &= m;else { // reject over-represented candidatesfor (int u = r >>> 1;u + m - (r = u % bound) < 0;u = mix32(nextSeed()) >>> 1);}return r;}

    ?

    總結

    以上是生活随笔為你收集整理的Java中随机数的原理,以及使用时的注意点的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。