Random在高併發下的缺陷以及JUC對其的優化

2021-09-22 12:34:08 字數 2921 閱讀 6103

public static void main(string args)
public static void main(string args) 

}

random類除了提供無參的構造方法以外,還提供了有參的構造方法,我們可以傳入int型別的引數,這個引數就被稱為「種子」,這樣「種子」就固定了,生成的隨機數也都是一樣了。

讓我們簡單的看下nextint的原始碼把,原始碼涉及到演算法,當然演算法不是本篇部落格討論的重點,我們可以把原始碼簡化成如下的樣子:

public int nextint(int bound)
首先是根據老的種子生成新的種子,然後是根據新的種子計算出隨機數,next***的核心**可以被簡化這兩步。現在讓我們想乙個問題,如果在高併發的情況下,有n個執行緒,同時執行到第一步:根據老的種子生成新的種子,獲得的種子不就一樣了嗎?由於第二步是根據新的種子來計算隨機數,這個演算法又是固定的,會產生什麼情況?n個執行緒最終獲得的隨機數不都一樣了嗎?顯然這不是我們想要的,所以jdk開發人員想到了這點,讓我們看看next方法裡面做了什麼:

protected int next(int bits)  while (!seed.compareandset(oldseed, nextseed));//cas,如果seed的值還是為oldseed,就用nextseed替換掉,並且返回true,退出while迴圈,如果已經不為oldseed了,就返回false,繼續迴圈

return (int)(nextseed >>> (48 - bits));//乙個神秘的演算法

}

定義了舊種子oldseed,下乙個種子(新種子)nextseed。

獲得舊的種子的值,賦值給oldseed 。

乙個神秘的演算法,計算出下乙個種子(新種子)賦值給nextseed。

使用cas操作,如果seed的值還是oldseed,就用nextseed替換掉,並且返回true,!true為false,退出while迴圈;如果seed的值已經不為oldseed了,就說明seed的值已經被替換過了,返回false,!false為true,繼續下一次while迴圈。

乙個神秘的演算法,根據nextseed計算出隨機數,並且返回。

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

private final atomiclong seed;
seed的型別是atomiclong,是乙個原子操作類,可以保證原子性,seed.get就是獲得seed具體的值,seed就是我們所謂的種子,也就是種子值儲存在了原子變數裡面。當有兩個執行緒同時進入到next方法,會發生如下的事情:

執行緒a,執行緒b同時拿到了seed的值,賦值給oldseed變數。

根據乙個神秘的演算法,計算出nextseed為***。注意,既然這個演算法是固定的,那麼生成出來的nextseed也必定是固定的。

進入while迴圈:3.1  執行緒a,利用cas演算法,發現seed的值還是為oldseed,說明seed的值沒有被替換過,就把seed的值替換成第二步生成出來的nextseed,替換成功,返回true,!true為false,退出while迴圈。3.2 執行緒b,利用cas演算法,發現seed的值已經不為oldseed了,因為執行緒a已經把seed的值替換成了nextseed了啊,所以cas失敗,只能再次迴圈。再次迴圈的時候, seed.get()就拿到了最新的種子值,再次根據乙個神秘的演算法獲得了nextseed,cas成功,退出迴圈。

看起來一切很美好,其實不然,如果併發很高,會發生什麼?大量的執行緒都在進行while迴圈,這是相當占用cpu的,所以juc推出了threadlocalrandom來解決這個問題。

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

public static void main(string args) 

}

可以看到使用方式發生了些許的改變,我們來看看threadlocalrandom核心**的實現邏輯:

current

public static threadlocalrandom current()
有一點需要注意,由於current是乙個靜態的方法,所以多次呼叫此方法,返回的threadlocalrandom物件是同乙個。

如果當前執行緒的probe是0,說明是第一次呼叫current方法,那麼需要呼叫localinit方法,否則直接返回已經產生的例項。

localinit

static final void localinit()
首先初始化probe和seed,隨後呼叫unsafe類的方法,把probe和seed設定到當前的執行緒,這樣其他執行緒就拿不到了。

nextint

public int nextint(int bound) 

return r;

}

和random類下的next***方法的原理一樣,也是根據舊的種子生成新的種子,然後根據新的種子來生成隨機數,我們來看下nextseed方法做了什麼:

nextseed

final long nextseed()
首先使用unsafe.getlong(t, seed) 來獲得當前執行緒的seed,隨後+上gamma來作為新的種子值,隨後將新的種子值放到當前執行緒中。

本文首先簡單的分析了random的實現原理,引出next***方法在高併發下的缺陷:需要競爭種子原子變數。接著介紹了threadlocalrandom的使用方法以及原理,從類的命名,就可以看出實現原理類似於threadlocal,seed種子是儲存在每個執行緒中的,也是根據每個執行緒中的seed來計算新的種子的,這樣就避免了競爭的問題。

高併發下的HashMap

1.hashmap在插入元素過多的時候需要進行resize,resize的條件是 hashmap.size capacity loadfactor。2.hashmap的resize包含擴容和rehash兩個步驟,rehash在併發的情況下可能會形成鍊錶環 hashmap進行儲存時,假設size超過當...

高併發下的MySQL

對於遊戲來說,db存在大量的insert update 可謂玩家的很多動作都會與db溝通。本文暫時忽略os 中的 io利用率,網絡卡流量,cpu變化情況,介紹如何檢視mysql部分引數 檢視每秒事務數 show global status like com commit show global st...

Redis在高併發下存在的問題

描述 在某些特定環境下,無論是先更新redis還是更新資料庫,兩者的資料都有可能不一致。解決方案1 雙寫模式 解決方案2 失效模式 最終解決方案 無論是雙寫模式還是失效模式,都會導致快取的不一致問題。即多個例項同時更新會出事,怎麼辦?如果是使用者緯度資料 訂單資料 使用者資料 這種併發機率非常小,不...