Gids voor ThreadLocalRandom in Java

1. Overzicht

Het genereren van willekeurige waarden is een veel voorkomende taak. Dit is de reden waarom Java het java.util.Random klasse.

Deze klasse presteert echter niet goed in een omgeving met meerdere threads.

Op een vereenvoudigde manier is de reden voor de slechte prestaties van Willekeurig in een omgeving met meerdere threads is te wijten aan twist - aangezien meerdere threads hetzelfde delen Willekeurig voorbeeld.

Om die beperking aan te pakken, Java introduceerde het java.util.concurrent.ThreadLocalRandom class in JDK 7 - voor het genereren van willekeurige getallen in een omgeving met meerdere threads.

Laten we eens kijken hoe ThreadLocalRandom presteert en hoe het te gebruiken in real-world applicaties.

2. ThreadLocalRandom Over Willekeurig

ThreadLocalRandom is een combinatie van de ThreadLocal en Willekeurig klassen (hierover later meer) en is geïsoleerd voor de huidige thread. Het behaalt dus betere prestaties in een multithread-omgeving door simpelweg gelijktijdige toegang tot instanties van Willekeurig.

Het willekeurige nummer dat door de ene thread wordt verkregen, wordt niet beïnvloed door de andere thread, terwijl java.util.Random biedt wereldwijd willekeurige getallen.

Ook in tegenstelling tot Willekeurig,ThreadLocalRandom ondersteunt niet het expliciet instellen van het zaad. In plaats daarvan overschrijft het de setSeed (lang zaad) methode geërfd van Willekeurig om altijd een UnsupportedOperationException indien gebeld.

2.1. Discussie over discussies

Tot nu toe hebben we vastgesteld dat de Willekeurig class presteert slecht in zeer gelijktijdige omgevingen. Laten we, om dit beter te begrijpen, eens kijken hoe een van de primaire bewerkingen, volgende (int), is geïmplementeerd:

particuliere finale AtomicLong zaad; protected int next (int bits) {long oldseed, nextseed; AtomicLong zaad = this.seed; do {oldseed = seed.get (); nextseed = (oldseed * vermenigvuldiger + addend) & mask; } while (! seed.compareAndSet (oldseed, nextseed)); return (int) (nextseed >>> (48 - bits)); }

Dit is een Java-implementatie voor het algoritme Linear Congruential Generator. Het is duidelijk dat alle threads hetzelfde delen zaad instantievariabele.

Om de volgende willekeurige set bits te genereren, probeert het eerst de gedeelde bits te wijzigen zaad waarde atomair via vergelijkAndSet of CAS in het kort.

Wanneer meerdere threads proberen het zaad gelijktijdig gebruikmakend van CAS, wint één thread en werkt het zaad, en de rest verliest. Bij het verliezen van threads wordt hetzelfde proces steeds opnieuw geprobeerd totdat ze de kans krijgen om de waarde en genereer uiteindelijk het willekeurige nummer.

Dit algoritme is vergrendelingsvrij en verschillende threads kunnen gelijktijdig worden uitgevoerd. Echter, wanneer de stelling hoog is, zal het aantal CAS-fouten en nieuwe pogingen de algehele prestaties aanzienlijk schaden.

Aan de andere kant is het ThreadLocalRandom verwijdert deze stelling volledig, aangezien elke thread zijn eigen exemplaar heeft van Willekeurig en bijgevolg zijn eigen beperkt zaad.

Laten we nu eens kijken naar enkele manieren om willekeurig te genereren int, lang en dubbele waarden.

3. Willekeurige waarden genereren met ThreadLocalRandom

Volgens de Oracle-documentatie, we hoeven alleen maar te bellen ThreadLocalRandom.current () methode, en het retourneert de instantie van ThreadLocalRandom voor de huidige thread. We kunnen dan willekeurige waarden genereren door beschikbare instantiemethoden van de klasse aan te roepen.

Laten we een random genereren int waarde zonder enige beperking:

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

Laten we vervolgens kijken hoe we een random bounded kunnen genereren int waarde, wat een waarde betekent tussen een bepaalde onder- en bovengrens.

Hier is een voorbeeld van het genereren van een random int waarde tussen 0 en 100:

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

Let op: 0 is de inclusief ondergrens en 100 is de exclusieve bovengrens.

We kunnen willekeurige waarden genereren voor lang en dubbele door een beroep te doen op volgendeLong () en volgendeDubbel () methoden op een vergelijkbare manier als getoond in de bovenstaande voorbeelden.

Java 8 voegt ook het volgendeGaussian () methode om de volgende normaal verdeelde waarde te genereren met een gemiddelde van 0,0 en een standaarddeviatie van 1,0 van de sequentie van de generator.

Net als bij de Willekeurig klasse, kunnen we ook de verdubbelt (), ints () en verlangt () methoden om stromen willekeurige waarden te genereren.

4. Vergelijken ThreadLocalRandom en Willekeurig Met behulp van JMH

Laten we eens kijken hoe we willekeurige waarden kunnen genereren in een omgeving met meerdere threads, door de twee klassen te gebruiken, en vervolgens hun prestaties kunnen vergelijken met JMH.

Laten we eerst een voorbeeld maken waarin alle threads één exemplaar delen van Willekeurig. Hier voeren we de taak in om een ​​willekeurige waarde te genereren met behulp van de Willekeurig instantie naar een UitvoerderService:

ExecutorService executor = Executors.newWorkStealingPool (); Lijst callables = nieuwe ArrayList (); Random random = nieuw Random (); for (int i = 0; i {return random.nextInt ();}); } executor.invokeAll (callables);

Laten we de prestaties van de bovenstaande code controleren met behulp van JMH-benchmarking:

# Rennen voltooid. Totale tijd: 00:00:36 Benchmarkmodus Cnt Score Fout Eenheden ThreadLocalRandomBenchMarker.randomValuesUsingRandom gemiddelde 20771.613 ± 222.220 us / op

Evenzo gaan we nu gebruiken ThreadLocalRandom in plaats van de Willekeurig instantie, die één instantie van ThreadLocalRandom voor elke draad in het zwembad:

ExecutorService executor = Executors.newWorkStealingPool (); Lijst callables = nieuwe ArrayList (); for (int i = 0; i {return ThreadLocalRandom.current (). nextInt ();}); } executor.invokeAll (callables);

Dit is het resultaat van het gebruik van ThreadLocalRandom:

# Rennen voltooid. Totale tijd: 00:00:36 Benchmarkmodus Cnt Score Fout Eenheden ThreadLocalRandomBenchMarker.randomValuesUsingThreadLocalRandom gemiddelde 20624.911 ± 113.268 us / op

Ten slotte, door de JMH-resultaten hierboven voor beide te vergelijken Willekeurig en ThreadLocalRandomkunnen we duidelijk zien dat de gemiddelde tijd die nodig is om 1000 willekeurige waarden te genereren met Willekeurig is 772 microseconden, terwijl ThreadLocalRandom het is ongeveer 625 microseconden.

We kunnen dus concluderen dat ThreadLocalRandom is efficiënter in een sterk gelijktijdige omgeving.

Om meer te weten te komen over JMH, bekijk hier ons vorige artikel.

5. Implementatiegegevens

Het is een goed mentaal model om een ThreadLocalRandom als een combinatie van ThreadLocal en Willekeurig klassen. In feite was dit mentale model afgestemd op de daadwerkelijke implementatie vóór Java 8.

Vanaf Java 8 is deze uitlijning echter volledig afgebroken als de ThreadLocalRandom werd een singleton. Hier is hoe de actueel() methode kijkt in Java 8+:

statische laatste instantie ThreadLocalRandom = nieuwe ThreadLocalRandom (); openbare statische ThreadLocalRandom current () {if (U.getInt (Thread.currentThread (), PROBE) == 0) localInit (); terugkeer instantie; }

Het is waar dat het delen van een global Willekeurig instantie leidt tot suboptimale prestaties bij hoge conflicten. Het gebruik van één speciale instantie per thread is echter ook overdreven.

In plaats van een speciaal exemplaar van Willekeurig per thread, elke thread hoeft alleen zijn eigen te onderhouden zaad waarde. Vanaf Java 8 is het Draad klasse zelf is achteraf ingebouwd om het zaad waarde:

openbare klasse Thread implementeert Runnable {// weggelaten @ jdk.internal.vm.annotation.Contended ("tlr") long threadLocalRandomSeed; @ jdk.internal.vm.annotation.Contended ("tlr") int threadLocalRandomProbe; @ jdk.internal.vm.annotation.Contended ("tlr") int threadLocalRandomSecondarySeed; }

De threadLocalRandomSeed variabele is verantwoordelijk voor het handhaven van de huidige seed-waarde voor ThreadLocalRandom. Bovendien is het secundaire zaad, threadLocalRandomSecondarySeed, wordt meestal intern gebruikt door mensen als ForkJoinPool.

Deze implementatie bevat een aantal optimalisaties die gemaakt moeten worden ThreadLocalRandom nog performanter:

  • Vermijd vals delen door de @Tevreden annotatie, die in feite voldoende opvulling toevoegt om de betwiste variabelen in hun eigen cacheregels te isoleren
  • Gebruik makend van zon.misc. onveilig om deze drie variabelen bij te werken in plaats van de Reflection API te gebruiken
  • Het vermijden van extra hash-lookups die zijn gekoppeld aan het ThreadLocal implementatie

6. Conclusie

Dit artikel illustreerde het verschil tussen java.util.Random en java.util.concurrent.ThreadLocalRandom.

We zagen ook het voordeel van ThreadLocalRandom over- Willekeurig in een omgeving met meerdere threads, evenals de prestaties en hoe we willekeurige waarden kunnen genereren met behulp van de class.

ThreadLocalRandom is een eenvoudige toevoeging aan de JDK, maar het kan een opmerkelijke impact hebben in zeer gelijktijdige toepassingen.

En, zoals altijd, is de implementatie van al deze voorbeelden te vinden op GitHub.