亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

深入淺析Random類在高并發(fā)下的缺陷及JUC對其的優(yōu)化

 更新時間:2019年04月24日 17:52:42   作者:CodeBear  
這篇文章主要介紹了Random類在高并發(fā)下的缺陷及JUC對其的優(yōu)化 ,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下

Random可以說是每個開發(fā)都知道,而且都用的很6的類,如果你說,你沒有用過Random,也不知道Random是什么鬼,那么你也不會來到這個技術(shù)類型的社區(qū),也看不到我的博客了。但并不是每個人都知道Random的原理,知道Random在高并發(fā)下的缺陷的人應(yīng)該更少。這篇,我就來分析下Random類在并發(fā)下的缺陷以及JUC對其的優(yōu)化。

Random的原理及缺陷

 public static void main(String[] args) {
  Random random = new Random();
  System.out.println(random.nextInt(100));
 }

在學(xué)習(xí)編程的時候,我一直對JDK開發(fā)人員很不解:為什么產(chǎn)生隨機數(shù)的方法名是:“”nextXXX”?雖然我英語只停留“點頭yes,搖頭no,來是come,去是go” 的水平,但是我知道next是“下一個”的意思,如果我來命名,會命名為“create”,“generate”,這樣不是更“貼切”嗎?為什么JDK開發(fā)人員會命名為“nextXXX”呢?難道是他們突然“詞窮”了,想不出什么單詞了,所以干脆隨便來一個?后來才知道,原來通過Random生成的隨機數(shù),并不是真正的隨機,它有一個種子的概念,是根據(jù)種子值來計算【下一個】值的,如果種子值相同,那么它生成出來的隨機數(shù)也必定相等,也就是“確定的輸入產(chǎn)生確定的輸出”。

如果你不信的話,我們可以來做一個實驗:

 public static void main(String[] args) {
  for (int i = 0; i < 10; i++) {
   Random random = new Random(15);
   System.out.println(random.nextInt(100));
  }
 }

Random類除了提供無參的構(gòu)造方法以外,還提供了有參的構(gòu)造方法,我們可以傳入int類型的參數(shù),這個參數(shù)就被稱為“種子”,這樣“種子”就固定了,生成的隨機數(shù)也都是一樣了:

讓我們簡單的看下nextInt的源碼把,源碼涉及到算法,當然算法不是本篇博客討論的重點,我們可以把源碼簡化成如下的樣子:

 public int nextInt(int bound) {
  if (bound <= 0)
   throw new IllegalArgumentException(BadBound);
  //1.根據(jù)老的種子生成新的種子
  int r = next(31);
  //2.根據(jù)新的種子計算隨機數(shù)
  ...
  return r;
 }

首先是根據(jù)老的種子生成新的種子,然后是根據(jù)新的種子計算出隨機數(shù),nextXXX的核心代碼可以被簡化這兩步。
 現(xiàn)在讓我們想一個問題,如果在高并發(fā)的情況下,有N個線程,同時執(zhí)行到第一步:根據(jù)老的種子生成新的種子,獲得的種子不就一樣了嗎?由于第二步是根據(jù)新的種子來計算隨機數(shù),這個算法又是固定的,會產(chǎn)生什么情況?N個線程最終獲得的隨機數(shù)不都一樣了嗎?顯然這不是我們想要的,所以JDK開發(fā)人員想到了這點,讓我們看看next方法里面做了什么:

protected int next(int bits) {
  long oldseed, nextseed;//定義舊種子,下一個種子
  AtomicLong seed = this.seed;
  do {
   oldseed = seed.get();//獲得舊的種子值,賦值給oldseed 
   nextseed = (oldseed * multiplier + addend) & mask;//一個神秘的算法
  } while (!seed.compareAndSet(oldseed, nextseed));//CAS,如果seed的值還是為oldseed,就用nextseed替換掉,并且返回true,退出while循環(huán),如果已經(jīng)不為oldseed了,就返回false,繼續(xù)循環(huán)
  return (int)(nextseed >>> (48 - bits));//一個神秘的算法
 }

1.定義了舊種子oldseed,下一個種子(新種子)nextseed。

2.獲得舊的種子的值,賦值給oldseed 。
3.一個神秘的算法,計算出下一個種子(新種子)賦值給nextseed。
4.使用CAS操作,如果seed的值還是oldseed,就用nextseed替換掉,并且返回true,!true為false,退出while循環(huán);如果seed的值已經(jīng)不為oldseed了,就說明seed的值已經(jīng)被替換過了,返回false,!false為true,繼續(xù)下一次while循環(huán)。
5.一個神秘的算法,根據(jù)nextseed計算出隨機數(shù),并且返回。

我們可以看到核心就在第四步,我再來更詳細的的描述下,首先要知道seed的類型:
 private final AtomicLong seed;

seed的類型是AtomicLong,是一個原子操作類,可以保證原子性,seed.get就是獲得seed具體的值,seed就是我們所謂的種子,也就是種子值保存在了原子變量里面。

 當有兩個線程同時進入到next方法,會發(fā)生如下的事情:

1.線程A,線程B同時拿到了seed的值,賦值給oldseed變量。

2.根據(jù)一個神秘的算法,計算出nextseed為XXX。注意,既然這個算法是固定的,那么生成出來的nextseed也必定是固定的。

3.進入while循環(huán):

3.1 線程A,利用CAS算法,發(fā)現(xiàn)seed的值還是為oldseed,說明seed的值沒有被替換過,就把seed的值替換成第二步生成出來的nextseed,替換成功,返回true,!true為false,退出while循環(huán)。
3.2 線程B,利用CAS算法,發(fā)現(xiàn)seed的值已經(jīng)不為oldseed了,因為線程A已經(jīng)把seed的值替換成了nextseed了啊,所以CAS失敗,只能再次循環(huán)。再次循環(huán)的時候, seed.get()就拿到了最新的種子值,再次根據(jù)一個神秘的算法獲得了nextSeed,CAS成功,退出循環(huán)。

看起來一切很美好,其實不然,如果并發(fā)很高,會發(fā)生什么?大量的線程都在進行while循環(huán),這是相當占用CPU的,所以JUC推出了ThreadLocalRandom來解決這個問題。

ThreadLocalRandom

首先,讓我們來看看ThreadLocalRandom的使用方法:

public static void main(String[] args) {
  for (int i = 0; i < 10; i++) {
   ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
   System.out.println(threadLocalRandom.nextInt(100));
  }
 }

可以看到使用方式發(fā)生了些許的改變,我們來看看ThreadLocalRandom核心代碼的實現(xiàn)邏輯:

current

  public static ThreadLocalRandom current() {
    if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
      localInit();
    return instance;
  }

有一點需要注意,由于current是一個靜態(tài)的方法,所以多次調(diào)用此方法,返回的ThreadLocalRandom對象是同一個。

如果當前線程的PROBE是0,說明是第一次調(diào)用current方法,那么需要調(diào)用localInit方法,否則直接返回已經(jīng)產(chǎn)生的實例。

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

首先初始化probe和seed,隨后調(diào)用UNSAFE類的方法,把probe和seed設(shè)置到當前的線程,這樣其他線程就拿不到了。

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

和Random類下的nextXXX方法的原理一樣,也是根據(jù)舊的種子生成新的種子,然后根據(jù)新的種子來生成隨機數(shù),我們來看下nextSeed方法做了什么:

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

首先使用UNSAFE.getLong(t, SEED) 來獲得當前線程的SEED,隨后+上GAMMA來作為新的種子值,隨后將新的種子值放到當前線程中。

總結(jié)

本文首先簡單的分析了Random的實現(xiàn)原理,引出nextXXX方法在高并發(fā)下的缺陷:需要競爭種子原子變量。接著介紹了ThreadLocalRandom的使用方法以及原理,從類的命名,就可以看出實現(xiàn)原理類似于ThreadLocal,seed種子是保存在每個線程中的,也是根據(jù)每個線程中的seed來計算新的種子的,這樣就避免了競爭的問題。

相關(guān)文章

  • springBoot啟動報錯log4j沖突的解決方案

    springBoot啟動報錯log4j沖突的解決方案

    這篇文章主要介紹了springBoot啟動報錯log4j沖突的解決方案,具有很好的參考價值,希望對大家有所幫助。
    2021-07-07
  • 詳解Java的線程的優(yōu)先級以及死鎖

    詳解Java的線程的優(yōu)先級以及死鎖

    這篇文章主要介紹了詳解Java的線程的優(yōu)先級以及死鎖,線程是Java編程學(xué)習(xí)中的重要知識,需要的朋友可以參考下
    2015-09-09
  • IntelliJ IDEA優(yōu)化配置的實現(xiàn)

    IntelliJ IDEA優(yōu)化配置的實現(xiàn)

    這篇文章主要介紹了IntelliJ IDEA優(yōu)化配置的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • 以用戶名注冊為例分析三種Action獲取數(shù)據(jù)的方式

    以用戶名注冊為例分析三種Action獲取數(shù)據(jù)的方式

    這篇文章主要介紹了以用戶名注冊為例分析三種Action獲取數(shù)據(jù)的方式的相關(guān)資料,需要的朋友可以參考下
    2016-03-03
  • MyBatis-Plus?分頁查詢的實現(xiàn)示例

    MyBatis-Plus?分頁查詢的實現(xiàn)示例

    本文主要介紹了MyBatis-Plus?分頁查詢的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Java8?Stream?collect(Collectors.toMap())的使用

    Java8?Stream?collect(Collectors.toMap())的使用

    這篇文章主要介紹了Java8?Stream?collect(Collectors.toMap())的使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • Java?CAS機制詳解

    Java?CAS機制詳解

    這篇文章主要介紹了Java?CAS機制,CAS機制是一種數(shù)據(jù)更新的方式。在具體講什么是CAS機制之前,我們先來聊下在多線程環(huán)境下,對共享變量進行數(shù)據(jù)更新的兩種模式:悲觀鎖模式和樂觀鎖模式
    2023-01-01
  • 使用springboot打包后的文件讀取方式

    使用springboot打包后的文件讀取方式

    這篇文章主要介紹了使用springboot打包后的文件讀取方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • java編程數(shù)據(jù)類型全面詳解教程新手必入

    java編程數(shù)據(jù)類型全面詳解教程新手必入

    這篇文章主要為大家介紹了java編程數(shù)據(jù)類型全面詳解教程,強烈推薦新手入,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步
    2021-10-10
  • MyBatis-Plus 通用IService使用詳解

    MyBatis-Plus 通用IService使用詳解

    這篇文章主要介紹了MyBatis-Plus 通用IService使用詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08

最新評論