电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

java高并發情況下高效的隨機數生成器

瀏覽:2日期:2022-08-14 17:17:33
前言

在代碼中生成隨機數,是一個非常常用的功能,并且JDK已經提供了一個現成的Random類來實現它,并且Random類是線程安全的。

下面是Random.next()生成一個隨機整數的實現:

protected int next(int bits) { long oldseed, nextseed; AtomicLong seed = this.seed; do {oldseed = seed.get();nextseed = (oldseed * multiplier + addend) & mask; //CAS 有競爭是效率低下 } while (!seed.compareAndSet(oldseed, nextseed)); return (int)(nextseed >>> (48 - bits));}

不難看到,上面的方法中使用CAS操作更新seed,在大量線程競爭的場景下,這個CAS操作很可能失敗,失敗了就會重試,而這個重試又會消耗CPU運算,從而使得性能大大下降了。

因此,雖然Random是線程安全的,但是并不是“高并發”的。

為了改進這個問題,增強隨機數生成器在高并發環境中的性能,于是乎,就有了ThreadLocalRandom——一個性能強悍的高并發隨機數生成器。

ThreadLocalRandom繼承自Random,根據里氏代換原則,這說明ThreadLocalRandom提供了和Random相同的隨機數生成功能,只是實現算法略有不同。

在Thread中的變量

為了應對線程競爭,Java中有一個ThreadLocal類,為每一個線程分配了一個獨立的,互不相干的存儲空間。

ThreadLocal的實現依賴于Thread對象中的ThreadLocal.ThreadLocalMap threadLocals成員字段。

與之類似,為了讓隨機數生成器只訪問本地線程數據,從而避免競爭,在Thread中,又增加了3個成員:

/** The current seed for a ThreadLocalRandom */@sun.misc.Contended('tlr')long threadLocalRandomSeed;/** Probe hash value; nonzero if threadLocalRandomSeed initialized */@sun.misc.Contended('tlr')int threadLocalRandomProbe;/** Secondary seed isolated from public ThreadLocalRandom sequence */@sun.misc.Contended('tlr')int threadLocalRandomSecondarySeed;

這3個字段作為Thread類的成員,便自然和每一個Thread對象牢牢得捆綁在一起,因此成為了名副其實的ThreadLocal變量,而依賴這幾個變量實現的隨機數生成器,也就成為了ThreadLocalRandom。

消除偽共享

不知道大家有沒有注意到, 在這些變量上面,都帶有一個注解@sun.misc.Contended,這個注解是干什么用的呢?要了解這個,大家得先知道一下并發編程中的一個重要問題——偽共享

我們知道,CPU是不直接訪問內存的,數據都是從高速緩存中加載到寄存器的,高速緩存又有L1,L2,L3等層級。在這里,我們先簡化這些負責的層級關系,假設只有一級緩存和一個主內存。

CPU讀取和更新緩存的時候,是以行為單位進行的,也叫一個cache line,一行一般64字節,也就是8個long的長度。

因此,問題就來了,一個緩存行可以放多個變量,如果多個線程同時訪問的不同的變量,而這些不同的變量又恰好位于同一個緩存行,那會發生什么呢?

java高并發情況下高效的隨機數生成器

如上圖所示,X,Y為相鄰2個變量,位于同一個緩存行,兩個CPU core1 core2都加載了他們,core1更新X,同時,core2更新Y,由于數據的讀取和更新是以緩存行為單位的,這就意味著當這2件事同時發生時,就產生了競爭,導致core1和core2有可能需要重新刷新自己的數據(緩存行被對方更新了),這就導致系統的性能大大折扣,這就是偽共享問題。

那怎么改進呢?如下圖:

java高并發情況下高效的隨機數生成器

上圖中,我們把X單獨占用一個緩存行,Y單獨占用一個緩存行,這樣各自更新和讀取,都不會有任何影響了。

而上述代碼中的@sun.misc.Contended(“tlr”)就會在虛擬機層面,幫助我們在變量的前后生成一些padding,使得被標注的變量位于同一個緩存行,不與其它變量沖突。

在Thread對象中,成員變量threadLocalRandomSeed,threadLocalRandomProbe,threadLocalRandomSecondarySeed被標記為同一個組tlr,使得這3個變量放置于一個單獨的緩存行,而不與其它變量發生沖突,從而提高在并發環境中的訪問速度。

反射的高效替代方案

隨機數的產生需要訪問Thread的threadLocalRandomSeed等成員,但是考慮到類的封裝性,這些成員卻是包內可見的。

很不幸,ThreadLocalRandom位于java.util.concurrent包,而Thread則位于java.lang包,因此,ThreadLocalRandom并沒有辦法訪問Thread的threadLocalRandomSeed等變量。

這時,Java老鳥們可能就會跳出來說:這算什么,看我的反射大法,不管啥都能摳出來訪問一下。

說的不錯,反射是一種可以繞過封裝,直接訪問對象內部數據的方法,但是,反射的性能不太好,并不適合作為一個高性能的解決方案。

有沒有什么辦法可以讓ThreadLocalRandom訪問Thread的內部成員,同時又具有遠超于反射的,且無限接近于直接變量訪問的方法呢?答案是肯定的,這就是使用Unsafe類。

這里,就簡單介紹一下用的兩個Unsafe的方法:

public native long getLong(Object o, long offset);public native void putLong(Object o, long offset, long x);

其中getLong()方法,會讀取對象o的第offset字節偏移量的一個long型數據;putLong()則會將x寫入對象o的第offset個字節的偏移量中。

這類類似C的操作方法,帶來了極大的性能提升,更重要的是,由于它避開了字段名,直接使用偏移量,就可以輕松繞過成員的可見性限制了。

性能問題解決了,那下一個問題是,我怎么知道threadLocalRandomSeed成員在Thread中的偏移位置呢,這就需要用unsafe的objectFieldOffset()方法了,請看下面的代碼:

java高并發情況下高效的隨機數生成器

上述這段static代碼,在ThreadLocalRandom類初始化的時候,就取得了Thread成員變量threadLocalRandomSeed,threadLocalRandomProbe,threadLocalRandomSecondarySeed在對象偏移中的位置。

因此,只要ThreadLocalRandom需要使用這些變量,都可以通過unsafe的getLong()和putLong()來進行訪問(也可能是getInt()和putInt())。

比如在生成一個隨機數的時候:

protected int next(int bits) { return (int)(mix64(nextSeed()) >>> (64 - bits));}final long nextSeed() { Thread t; long r; // read and update per-thread seed //在ThreadLocalRandom中,訪問了Thread的threadLocalRandomSeed變量 UNSAFE.putLong(t = Thread.currentThread(), SEED, r = UNSAFE.getLong(t, SEED) + GAMMA); return r;}

這種Unsafe的方法掉地能有多快呢,讓我們一起看做個試驗看看:

這里,我們自己寫一個ThreadTest類,使用反射和unsafe兩種方法,來不停讀寫threadLocalRandomSeed成員變量,比較它們的性能差異,代碼如下:

java高并發情況下高效的隨機數生成器

上述代碼中,分別使用反射方式byReflection() 和Unsafe的方式byUnsafe()來讀寫threadLocalRandomSeed變量1億次,得到的測試結果如下:

byUnsafe spend :171msbyReflection spend :645ms

不難看到,使用Unsafe的方法遠遠優于反射的方法,這也是JDK內部,大量使用Unsafe來替代反射的原因之一。

隨機數種子

我們知道,偽隨機數生成都需要一個種子,threadLocalRandomSeed和threadLocalRandomSecondarySeed就是這里的種子。其中threadLocalRandomSeed是long型的,threadLocalRandomSecondarySeed是int。

threadLocalRandomSeed是使用最廣泛的大量的隨機數其實都是基于threadLocalRandomSeed的。而threadLocalRandomSecondarySeed只是某些特定的JDK內部實現中有使用,使用并不廣泛。

初始種子默認使用的是系統時間:

java高并發情況下高效的隨機數生成器

上述代碼中完成了種子的初始化,并將初始化的種子通過UNSAFE存在SEED的位置(即threadLocalRandomSeed)。

接著就可以使用nextInt()方法獲得隨機整數了:

public int nextInt() { return mix32(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;}

每一次調用nextInt()都會使用nextSeed()更新threadLocalRandomSeed。由于這是一個線程獨有的變量,因此完全不會有競爭,也不會有CAS的重試,性能也就大大提高了。

探針Probe的作用

除了種子外,還有一個threadLocalRandomProbe探針變量,這個變量是用來做什么的呢?

我們可以把threadLocalRandomProbe 理解為一個針對每個Thread的Hash值(不為0),它可以用來作為一個線程的特征值,基于這個值可以為線程在數組中找到一個特定的位置。

static final int getProbe() { return UNSAFE.getInt(Thread.currentThread(), PROBE);}

來看一個代碼片段:

CounterCell[] as; long b, s;if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { CounterCell a; long v; int m; boolean uncontended = true; if (as == null || (m = as.length - 1) < 0 ||// 使用probe,為每個線程找到一個在數組as中的位置// 由于每個線程的probe值不一樣,因此大概率 每個線程對應的數組中的元素也是不一樣的// 每個線程對應了不同的元素,就可以沒有沖突的進行完全的并發操作// 因此探針probe在這里 就起到了防止沖突的作用(a = as[ThreadLocalRandom.getProbe() & m]) == null ||!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {

在具體的實現中,如果上述代碼發生了沖突,那么,還可以使用ThreadLocalRandom.advanceProbe()方法來修改一個線程的探針值,這樣可以進一步避免未來可能得沖突,從而減少競爭,提高并發性能。

static final int advanceProbe(int probe) { //根據當前探針值,計算一個更新的探針值 probe ^= probe << 13; // xorshift probe ^= probe >>> 17; probe ^= probe << 5; //更新探針值到線程對象中 即修改了threadLocalRandomProbe變量 UNSAFE.putInt(Thread.currentThread(), PROBE, probe); return probe;}總結

今天,我們介紹了ThreadLocalRandom對象,這是一個高并發環境中的,高性能的隨機數生成器。

我們不但介紹了ThreadLocalRandom的功能和內部實現原理,還介紹介紹了ThreadLocalRandom對象是如何達到高性能的(比如通過偽共享,Unsafe等手段),希望大家可以將這些技術靈活運用到自己的工程中。

小傻瓜們對這個冷門類是否有深一步的理解了?理解了可以在評論區來一波:變得更強

我是敖丙,你知道的越多,不知道的越多,我們下期見。

以上就是java高并發情況下高效的隨機數生成器的詳細內容,更多關于java高并發高效隨機數的資料請關注好吧啦網其它相關文章!

標簽: Java
相關文章:
主站蜘蛛池模板: 安徽净化工程设计_无尘净化车间工程_合肥净化实验室_安徽创世环境科技有限公司 | 钢格板|镀锌钢格板|热镀锌钢格板|格栅板|钢格板|钢格栅板|热浸锌钢格板|平台钢格板|镀锌钢格栅板|热镀锌钢格栅板|平台钢格栅板|不锈钢钢格栅板 - 专业钢格板厂家 | 耐火浇注料价格-高强高铝-刚玉碳化硅耐磨浇注料厂家【直销】 | 医用酒精_84消毒液_碘伏消毒液等医用消毒液-漓峰消毒官网 | 智能垃圾箱|垃圾房|垃圾分类亭|垃圾分类箱专业生产厂家定做-宿迁市传宇环保设备有限公司 | 贴片电感_贴片功率电感_贴片绕线电感_深圳市百斯特电子有限公司 贴片电容代理-三星电容-村田电容-风华电容-国巨电容-深圳市昂洋科技有限公司 | 昆明网络公司|云南网络公司|昆明网站建设公司|昆明网页设计|云南网站制作|新媒体运营公司|APP开发|小程序研发|尽在昆明奥远科技有限公司 | LHH药品稳定性试验箱-BPS系列恒温恒湿箱-意大利超低温冰箱-上海一恒科学仪器有限公司 | 螺旋压榨机-刮泥机-潜水搅拌机-电动泥斗-潜水推流器-南京格林兰环保设备有限公司 | 专业生物有机肥造粒机,粉状有机肥生产线,槽式翻堆机厂家-郑州华之强重工科技有限公司 | 真空泵厂家_真空泵机组_水环泵_旋片泵_罗茨泵_耐腐蚀防爆_中德制泵 | 本安接线盒-本安电路用接线盒-本安分线盒-矿用电话接线盒-JHH生产厂家-宁波龙亿电子科技有限公司 | 光泽度计_测量显微镜_苏州压力仪_苏州扭力板手维修-苏州日升精密仪器有限公司 | 冷藏车-东风吸污车-纯电动环卫车-污水净化车-应急特勤保障车-程力专汽厂家-程力专用汽车股份有限公司销售二十一分公司 | 拉力机-拉力试验机-万能试验机-电子拉力机-拉伸试验机-剥离强度试验机-苏州皖仪实验仪器有限公司 | 硅PU球场、篮球场地面施工「水性、环保、弹性」硅PU材料生产厂家-广东中星体育公司 | 急救箱-应急箱-急救包厂家-北京红立方医疗设备有限公司 | 广东风淋室_广东风淋室厂家_广东风淋室价格_广州开源_传递窗_FFU-广州开源净化科技有限公司 | GAST/BRIWATEC/CINCINNATI/KARL-KLEIN/ZIEHL-ABEGG风机|亚喜科技 | 精密冲床,高速冲床等冲压设备生产商-常州晋志德压力机厂 | 模具ERP_模具管理系统_模具mes_模具进度管理_东莞市精纬软件有限公司 | 桁架机器人_桁架机械手_上下料机械手_数控车床机械手-苏州清智科技装备制造有限公司 | 创绿家招商加盟网-除甲醛加盟-甲醛治理加盟-室内除甲醛加盟-创绿家招商官网 | 新能源汽车电池软连接,铜铝复合膜柔性连接,电力母排-容发智能科技(无锡)有限公司 | 除甲醛公司-甲醛检测治理-杭州创绿家环保科技有限公司-室内空气净化十大品牌 | 沧州友城管业有限公司-内外涂塑钢管-大口径螺旋钢管-涂塑螺旋管-保温钢管生产厂家 | 100_150_200_250_300_350_400公斤压力空气压缩机-舰艇航天配套厂家 | 浙江华锤电器有限公司_地磅称重设备_防作弊地磅_浙江地磅售后维修_无人值守扫码过磅系统_浙江源头地磅厂家_浙江工厂直营地磅 | 高压微雾加湿器_工业加湿器_温室喷雾-昌润空气净化设备 | LHH药品稳定性试验箱-BPS系列恒温恒湿箱-意大利超低温冰箱-上海一恒科学仪器有限公司 | 带锯机|木工带锯机圆木推台锯|跑车带锯机|河北茂业机械制造有限公司| | 专业广州网站建设,微信小程序开发,一物一码和NFC应用开发、物联网、外贸商城、定制系统和APP开发【致茂网络】 | 120kv/2mA直流高压发生器-60kv/2mA-30kva/50kv工频耐压试验装置-旭明电工 | 防水套管_柔性防水套管_刚性防水套管-巩义市润达管道设备制造有限公司 | EDLC超级法拉电容器_LIC锂离子超级电容_超级电容模组_软包单体电容电池_轴向薄膜电力电容器_深圳佳名兴电容有限公司_JMX专注中高端品牌电容生产厂家 | 顶空进样器-吹扫捕集仪-热脱附仪-二次热解吸仪-北京华盛谱信仪器 | 上海冠顶工业设备有限公司-隧道炉,烘箱,UV固化机,涂装设备,高温炉,工业机器人生产厂家 | 海尔生物医疗四川代理商,海尔低温冰箱四川销售-成都壹科医疗器械有限公司 | 上海网站建设-上海网站制作-上海网站设计-上海做网站公司-咏熠软件 | 灌装封尾机_胶水灌装机_软管灌装封尾机_无锡和博自动化机械制造有限公司 | 带式压滤机_污泥压滤机_污泥脱水机_带式过滤机_带式压滤机厂家-河南恒磊环保设备有限公司 |