Útmutató a Java ThreadLocalRandomhoz

1. Áttekintés

A véletlenszerű értékek generálása nagyon gyakori feladat. Ezért a Java biztosítja a java.util.Random osztály.

Ez az osztály azonban nem teljesít jól egy több szálat tartalmazó környezetben.

Egyszerűsítve a gyenge teljesítmény oka Véletlen többszálas környezetben a versengés okozza - mivel több szál ugyanaz Véletlen példa.

A korlátozás kezelése érdekében Java bemutatta a java.util.concurrent.ThreadLocalRandom osztály a JDK 7-ben - véletlenszámok generálásához többszálas környezetben.

Lássuk, hogyan ThreadLocalRandom és hogyan kell használni a valós alkalmazásokban.

2. ThreadLocalRandom Felett Véletlen

ThreadLocalRandom kombinációja a ThreadLocal és Véletlen osztályok (erről bővebben később), és el van különítve az aktuális száltól. Így több szálú környezetben jobb teljesítményt ér el, ha egyszerűen elkerüli az egyidejű hozzáférést a Véletlen.

Az egyik szál által kapott véletlenszámot a másik szál nem befolyásolja, míg java.util.Random véletlenszámokat szolgáltat globálisan.

Továbbá, ellentétben Véletlen,ThreadLocalRandom nem támogatja a vetőmag kifejezett beállítását. Ehelyett felülírja a setSeed (hosszú mag) módszer örökölte Véletlen hogy mindig dobjon egy UnsupportedOperationException ha hívják.

2.1. Menetvita

Eddig megállapítottuk, hogy a Véletlen osztály rosszul teljesít erősen párhuzamos környezetben. Ennek jobb megértése érdekében nézzük meg, hogy az egyik elsődleges művelete, következő (int), megvalósul:

privát végleges AtomicLong mag; védett int következő (int bit) {hosszú vetőmag, nextseed; Atomhosszú mag = ez.mag; do {oldseed = mag.get (); nextseed = (oldseed * szorzó + addend) & mask; } while (! seed.compareAndSet (oldseed, nextseed)); return (int) (nextseed >>> (48 - bit)); }

Ez egy Java implementáció a Linear Congruential Generator algoritmushoz. Nyilvánvaló, hogy minden szál ugyanaz mag példány változó.

A következő véletlenszerű bitkészlet előállításához először megpróbálja megváltoztatni a megosztottat mag érték atomilag keresztül CompareAndSet vagy CAS röviden.

Ha több szál megkísérli frissíteni a mag a CAS használatával egy szál nyeri és frissíti a mag, a többi pedig veszít. A szálak elvesztése újra és újra megpróbálja ugyanazt a folyamatot, amíg esélyt kapnak az érték és a frissítésére végül előállítja a véletlen számot.

Ez az algoritmus zármentes, és különböző szálak haladhatnak egyidejűleg. Azonban, amikor nagy a verseny, a CAS kudarcok és újrapróbálkozások száma jelentősen rontja az általános teljesítményt.

Másrészt a ThreadLocalRandom teljesen eltávolítja ezt az állítást, mivel minden szálnak megvan a saját példánya Véletlen és következésképpen a sajátja korlátozott mag.

Most nézzük meg a véletlenszerű generálás néhány módját int, hosszú és kettős értékek.

3. Véletlen értékek előállítása ThreadLocalRandom

Az Oracle dokumentációja szerint csak hívnunk kell ThreadLocalRandom.current () metódust, és visszaadja a ThreadLocalRandom az aktuális szálhoz. Ezután véletlenszerű értékeket generálhatunk az osztály rendelkezésre álló metódusainak meghívásával.

Generáljunk véletlenszerűt int érték korlátok nélkül:

int unoundedRandomValue = ThreadLocalRandom.current (). nextInt ());

Ezután nézzük meg, hogyan generálhatunk véletlenszerű korlátot int érték, vagyis egy adott alsó és felső határ közötti érték.

Itt van egy példa egy véletlen generálására int 0 és 100 közötti érték:

int boundedRandomValue = ThreadLocalRandom.current (). nextInt (0, 100);

Felhívjuk figyelmét, hogy 0 a befogadó alsó határ, és 100 a kizárólagos felső határ.

Generálhatunk véletlenszerű értékeket a hosszú és kettős hivatkozással nextLong () és nextDouble () módszereket a fenti példákban bemutatott módon.

A Java 8 hozzáadja a következőGaussian () módszer a következő normálisan elosztott érték előállítására 0,0 átlaggal és 1,0 szórással a generátor szekvenciájától.

Mint a Véletlen osztályban, használhatjuk a páros (), ints () és vágyak () módszerek véletlenszerű értékek előállítására.

4. Összehasonlítás ThreadLocalRandom és Véletlen A JMH használata

Nézzük meg, hogyan generálhatunk véletlenszerű értékeket egy több szálat tartalmazó környezetben, a két osztály használatával, majd hasonlítsuk össze a teljesítményüket a JMH segítségével.

Először készítsünk egy példát, ahol az összes szál egyetlen példányát osztja meg Véletlen. Itt egy véletlenszerű érték előállításának feladatát adjuk be a Véletlen példány egy ExecutorService:

ExecutorService végrehajtó = Executors.newWorkStealingPool (); Lista callable = new ArrayList (); Véletlenszerű véletlenszerű = új Véletlenszerű (); for (int i = 0; i {return random.nextInt ();}); } végrehajtó.invokeAll (hívható);

Ellenőrizzük a fenti kód teljesítményét a JMH benchmarking segítségével:

# Futtatás kész. Teljes idő: 00:00:36 Benchmark Mode Cnt Pontszám Hibaegységek ThreadLocalRandomBenchMarker.randomValuesUsingRandom avgt 20 771,613 ± 222,220 us / op

Hasonlóképpen, most használjuk ThreadLocalRandom a helyett Véletlen példány, amely a ThreadLocalRandom a medence minden egyes szálához:

ExecutorService végrehajtó = Executors.newWorkStealingPool (); Lista callable = new ArrayList (); for (int i = 0; i {return ThreadLocalRandom.current (). nextInt ();}); } végrehajtó.invokeAll (hívható);

Itt van a használat eredménye ThreadLocalRandom:

# Futtatás kész. Teljes idő: 00:00:36 Benchmark Mode Cnt Pontszám Hibaegységek ThreadLocalRandomBenchMarker.randomValuesUsingThreadLocalRandom avgt 20 624.911 ± 113.268 us / op

Végül a fenti JMH-eredmények összehasonlításával mindkét esetben Véletlen és ThreadLocalRandom, világosan láthatjuk, hogy az 1000 véletlenszerű érték előállításához szükséges átlagos idő Véletlen 772 mikroszekundum, míg a ThreadLocalRandom 625 mikroszekundum körül van.

Így arra következtethetünk ThreadLocalRandom hatékonyabb, nagyon párhuzamos környezetben.

További információkért JMH, nézze meg itt korábbi cikkünket.

5. A megvalósítás részletei

Jó mentális modell gondolni a ThreadLocalRandom kombinációjaként ThreadLocal és Véletlen osztályok. Ami azt illeti, ez a mentális modell igazodott a Java 8 előtti tényleges megvalósításhoz.

A Java 8-tól kezdve ez az összehangolás teljesen megszakadt, mint a ThreadLocalRandom szingletté vált. Itt van, hogyan jelenlegi() A módszer Java 8+ verzióban néz ki:

statikus végleges ThreadLocalRandom példány = new ThreadLocalRandom (); public static ThreadLocalRandom current () {if (U.getInt (Thread.currentThread (), PROBE) == 0) localInit (); visszatérési példány; }

Igaz, hogy egy globális megosztása Véletlen az eset optimális teljesítményhez vezet magas verseny esetén. Azonban szálanként egy dedikált példány használata szintén túlzott.

Dedikált példánya helyett Véletlen szálanként minden szálnak csak a sajátját kell fenntartania mag érték. A Java 8-tól kezdve a cérna magát az osztályt utólag felszerelték a mag érték:

public class szál megvalósítja a Runnable {// kihagyva @ jdk.internal.vm.annotation.Contended ("tlr") hosszú szálatLocalRandomSeed; @ jdk.internal.vm.annotation.Contended ("tlr") int threadLocalRandomProbe; @ jdk.internal.vm.annotation.Contended ("tlr") int threadLocalRandomSecondarySeed; }

A threadLocalRandomSeed változó felelős a ThreadLocalRandom. Sőt, a másodlagos vetőmag, threadLocalRandomSecondarySeed, általában belsőleg használják a hasonlóak ForkJoinPool.

Ez a megvalósítás néhány optimalizálást tartalmaz ThreadLocalRandom még teljesítőbb:

  • Kerülje el a hamis megosztást a @Elégedett annotáció, amely alapvetően annyi betétet ad hozzá, hogy a vitatott változókat el tudják különíteni a saját gyorsítótárukból
  • Használata sun.misc.biztonságos frissítse ezt a három változót a Reflection API használata helyett
  • Kerülje a ThreadLocal végrehajtás

6. Következtetés

Ez a cikk szemléltette a különbséget java.util.Random és java.util.concurrent.ThreadLocalRandom.

Láttuk az előnyét is ThreadLocalRandom felett Véletlen többszálas környezetben, valamint a teljesítmény és az, hogy miként generálhatunk véletlenszerű értékeket az osztály segítségével.

ThreadLocalRandom a JDK egyszerű kiegészítése, de figyelemre méltó hatást hozhat létre a nagyon egyidejű alkalmazásokban.

És mint mindig, ezeknek a példáknak a megvalósítása megtalálható a GitHubon.