java并發(fā)高的情況下用ThreadLocalRandom來生成隨機數(shù)
一:簡述
如果我們想要生成一個隨機數(shù),通常會使用Random類。但是在并發(fā)情況下Random生成隨機數(shù)的性能并不是很理想,今天給大家介紹一下JUC包中的用于生成隨機數(shù)的類--ThreadLocalRandom.(本文基于JDK1.8)
二:Random的性能差在哪里
Random隨機數(shù)生成是和種子seed有關,而為了保證線程安全性,Random通過CAS機制來保證線程安全性。從next()方法中我們可以發(fā)現(xiàn)seed是通過自旋鎖和CAS來進行修改值的。如果在高并發(fā)的場景下,那么可能會導致CAS不斷失敗,從而導致不斷自旋,這樣就可能會導致服務器CPU過高。
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));
}三:ThreadLocalRandom的簡單使用
使用的方法很簡單,通過ThreadLocalRandom.current()獲取到ThreadLocalRandom實例,然后通過nextInt(),nextLong()等方法獲取一個隨機數(shù)。
代碼:
@Test
void test() throws InterruptedException {
new Thread(()->{
ThreadLocalRandom random = ThreadLocalRandom.current();
System.out.println(random.nextInt(100));
}).start();
new Thread(()->{
ThreadLocalRandom random = ThreadLocalRandom.current();
System.out.println(random.nextInt(100));
}).start();
Thread.sleep(100);
}運行結果:

四:為什么ThreadLocalRandom能在保證線程安全的情況下還能有不錯的性能
我們可以看一下ThreadLocalRandom的代碼實現(xiàn)。
首先我們很容易看出這是一個餓漢式的單例
/** Constructor used only for static singleton */
private ThreadLocalRandom() {
initialized = true; // false during super() call
}
/** The common ThreadLocalRandom */
static final ThreadLocalRandom instance = new ThreadLocalRandom();我們可以看到PROBE成員變量代表的是Thread類的threadLocalRandomProbe屬性的內(nèi)存偏移量,SEED成員變量代表的是Thread類的threadLocalRandomSeed屬性的內(nèi)存偏移量,SECONDARY成員變量代表的是Thread類的threadLocalRandomSecondarySeed屬性的內(nèi)存偏移量。
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception e) {
throw new Error(e);
}
}可以看到Thread類中確實有這三個屬性
Thread類:
@sun.misc.Contended("tlr")
//當前Thread的隨機種子 默認值是0
long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
//用來標志當前Thread的threadLocalRandomSeed是否進行了初始化 0代表沒有,非0代表已經(jīng)初始化 默認值是0
int threadLocalRandomProbe;
/** Secondary seed isolated from public ThreadLocalRandom sequence */
@sun.misc.Contended("tlr")
//當前Thread的二級隨機種子 默認值是0
int threadLocalRandomSecondarySeed;接下來我們看ThreadLocalRandom.current()方法。
ThreadLocalRandom.current()
ThreadLocalRandom.current()的作用主要是初始化隨機種子,并且返回ThreadLocalRandom的實例。
首先通過UNSAFE類獲取當前線程的Thread對象的threadLocalRandomProbe屬性,看隨機種子是否已經(jīng)初始化。沒有初始化,那么調(diào)用localInit()方法進行初始化
public static ThreadLocalRandom current() {
// 獲取當前線程的
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
localInit();
return instance;
}localInit()
localInit()方法的作用就是初始化隨機種子,可以看到代碼很簡單,就是通過UNSAFE類對當前Thread的threadLocalRandomProbe屬性和threadLocalRandomSeed屬性進行一個賦值。
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);
}接下來以nextInt()方法為例,看ThreadLocalRandom是如何生成到隨機數(shù)的。我們可以看出隨機數(shù)正是通過nextSeed()方法獲取到隨機種子,然后通過隨機種子而生成。所以重點看nextSeed()方法是如何獲取到隨機種子的。
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;
}nextSeed()
nextSeed()方法的作用是獲取隨機種子,代碼很簡單,就是通過UNSAFE類獲取當前線程的threadLocalRandomSeed屬性,并且將原來的threadLocalRandomSeed加上GAMMA設置成新的threadLocalRandomSeed。
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;
}小結:
ThreadLocalRandom為什么線程安全?是因為它將隨機種子保存在當前Thread對象的threadLocalRandomSeed變量中,這樣每個線程都有自己的隨機種子,實現(xiàn)了線程級別的隔離,所以ThreadLocalRandom也并不需要像Random通過自旋鎖和cas來保證隨機種子的線程安全性。在高并發(fā)的場景下,效率也會相對較高。
注:各位有沒有發(fā)現(xiàn)ThreadLocalRandom保證線程安全的方式和ThreadLocal有點像呢
需要注意的點:
1.ThreadLocalRandom是單例的。
2.我們每個線程在獲取隨機數(shù)之前都需要調(diào)用一下ThreadLocalRandom.current()來初始化當前線程的隨機種子。
3.理解ThreadLocalRandom需要對UnSafe類有所了解,它是Java提供的一個可以直接通過內(nèi)存對變量進行獲取和修改的一個工具類。java的CAS也是通過這個工具類來實現(xiàn)的。
到此這篇關于java并發(fā)高的情況下用ThreadLocalRandom來生成隨機數(shù)的文章就介紹到這了,更多相關java ThreadLocalRandom生成隨機數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot項目攔截器獲取Post方法的請求body實現(xiàn)
本文主要介紹了SpringBoot項目攔截器獲取Post方法的請求body,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01
Java多線程 兩階段終止模式Two-Phase Termination Patter
這篇文章主要介紹了Java多線程 兩階段終止模式Two-Phase Termination Patter,該模式有兩個角色,分別是Terminator,終止者,負責接收終止請求,執(zhí)行終止處理,處理完成后再終止自己。TerminationRequester終止請求發(fā)出者,用來向Terminator發(fā)出終止請求,需要的朋友可以參考一下2021-10-10
Java中BeanUtils.copyProperties基本用法與小坑
本文主要介紹了Java中BeanUtils.copyProperties基本用法與小坑,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-04-04
java?集合工具類Collections及Comparable和Comparator排序詳解
這篇文章主要介紹了java集合工具類Collections及Comparable和Comparator排序詳解,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-06-06
openEuler?搭建java開發(fā)環(huán)境的詳細過程
這篇文章主要介紹了openEuler?搭建java開發(fā)環(huán)境,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06

