Guide til ThreadLocalRandom i Java

1. Oversikt

Å generere tilfeldige verdier er en veldig vanlig oppgave. Dette er grunnen til at Java gir java.util.Random klasse.

Imidlertid fungerer denne klassen ikke godt i et miljø med flere tråder.

På en forenklet måte, årsaken til den dårlige ytelsen til Tilfeldig i et miljø med flere tråder skyldes strid - gitt at flere tråder deler det samme Tilfeldig forekomst.

For å takle den begrensningen, Java introduserte java.util.concurrent.ThreadLocalRandom klasse i JDK 7 - for å generere tilfeldige tall i et miljø med flere tråder.

La oss se hvordan ThreadLocalRandom utfører og hvordan du bruker den i virkelige applikasjoner.

2. ThreadLocalRandom Over Tilfeldig

ThreadLocalRandom er en kombinasjon av Trådlokal og Tilfeldig klasser (mer om dette senere) og er isolert til gjeldende tråd. Dermed oppnår det bedre ytelse i et flertrådet miljø ved ganske enkelt å unngå samtidig tilgang til forekomster av Tilfeldig.

Det tilfeldige tallet som oppnås av en tråd, påvirkes ikke av den andre tråden, mens java.util.Random gir tilfeldige tall globalt.

Også, i motsetning til Tilfeldig,ThreadLocalRandom støtter ikke innstilling av frø eksplisitt. I stedet overstyrer den setSeed (langt frø) metode arvet fra Tilfeldig å alltid kaste en Ikke-støttetOperationException hvis ringt.

2.1. Trådkonflikt

Så langt har vi slått fast at Tilfeldig klassen klarer seg dårlig i svært samtidige miljøer. For å bedre forstå dette, la oss se hvordan en av dens primære operasjoner, neste (int), er implementert:

privat finale AtomicLong seed; beskyttet int neste (int bits) {long oldseed, nextseed; AtomicLong seed = this.seed; gjør {oldseed = seed.get (); nextseed = (oldseed * multiplier + addend) & mask; } while (! seed.compareAndSet (oldseed, nextseed)); return (int) (nextseed >>> (48 - bits)); }

Dette er en Java-implementering for Linear Congruential Generator-algoritmen. Det er åpenbart at alle trådene deler det samme frø forekomstvariabel.

For å generere neste tilfeldige sett med bits, prøver den først å endre den delte frø verdi atomisk via sammenlignAndSet eller CAS for kort.

Når flere tråder prøver å oppdatere frø samtidig bruker CAS, en tråd vinner og oppdaterer frø, og resten taper. Å miste tråder vil prøve den samme prosessen om og om igjen til de får sjansen til å oppdatere verdien og genererer til slutt tilfeldig tall.

Denne algoritmen er låsfri, og forskjellige tråder kan utvikle seg samtidig. Derimot, når påstanden er høy, vil antall CAS-feil og prøvinger skade den samlede ytelsen betydelig.

På den annen side, den ThreadLocalRandom fjerner denne påstanden helt, ettersom hver tråd har sin egen forekomst av Tilfeldig og følgelig sin egen begrensede frø.

La oss nå se på noen av måtene å generere tilfeldig int, lang og dobbelt verdier.

3. Generere tilfeldige verdier ved hjelp av ThreadLocalRandom

I henhold til Oracle-dokumentasjonen, vi trenger bare å ringe ThreadLocalRandom.current () metode, og den vil returnere forekomsten av ThreadLocalRandom for gjeldende tråd. Vi kan deretter generere tilfeldige verdier ved å påkalle tilgjengelige forekomstmetoder i klassen.

La oss generere en tilfeldig int verdi uten grenser:

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

Deretter, la oss se hvordan vi kan generere en tilfeldig avgrensning int verdi, som betyr en verdi mellom en gitt nedre og øvre grense.

Her er et eksempel på å generere et tilfeldig int verdi mellom 0 og 100:

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

Vær oppmerksom på at 0 er den nedre grensen inklusive og 100 den øvre grensen.

Vi kan generere tilfeldige verdier for lang og dobbelt ved å påkalle nesteLang () og nextDouble () metoder på en lignende måte som vist i eksemplene ovenfor.

Java 8 legger også til nesteGaussisk () metode for å generere den neste normalt distribuerte verdien med et gjennomsnitt på 0,0 og 1,0 standardavvik fra generatorens sekvens.

Som med Tilfeldig klasse, kan vi også bruke dobler (), ints () og lengter () metoder for å generere strømmer av tilfeldige verdier.

4. Sammenligning ThreadLocalRandom og Tilfeldig Bruke JMH

La oss se hvordan vi kan generere tilfeldige verdier i et flertrådet miljø ved å bruke de to klassene, og deretter sammenligne ytelsen deres ved hjelp av JMH.

La oss først lage et eksempel der alle trådene deler en enkelt forekomst av Tilfeldig. Her sender vi oppgaven med å generere en tilfeldig verdi ved hjelp av Tilfeldig eksempel til en ExecutorService:

ExecutorService executor = Executors.newWorkStealingPool (); Liste callables = ny ArrayList (); Tilfeldig tilfeldig = ny Tilfeldig (); for (int i = 0; i {return random.nextInt ();}); } executor.invokeAll (callables);

La oss sjekke ytelsen til koden ovenfor ved hjelp av JMH benchmarking:

# Kjør komplett. Total tid: 00:00:36 Referansemodus Cnt Score Feilenheter TrådLocalRandomBenchMarker.randomValuesUsingRandom avgt 20 771.613 ± 222.220 us / op

På samme måte, la oss nå bruke ThreadLocalRandom i stedet for Tilfeldig forekomst, som bruker en forekomst av ThreadLocalRandom for hver tråd i bassenget:

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

Her er resultatet av bruk ThreadLocalRandom:

# Kjør komplett. Total tid: 00:00:36 Referansemodus Cnt Score Feilenheter TrådLocalRandomBenchMarker.randomValuesUsingTrådLocalRandom avgt 20 624.911 ± 113.268 us / op

Til slutt, ved å sammenligne JMH-resultatene ovenfor for begge Tilfeldig og ThreadLocalRandom, kan vi tydelig se at gjennomsnittlig tid det tar å generere 1000 tilfeldige verdier ved hjelp av Tilfeldig er 772 mikrosekunder, mens bruk ThreadLocalRandom det er rundt 625 mikrosekunder.

Dermed kan vi konkludere med det ThreadLocalRandom er mer effektiv i et svært samtidig miljø.

For å lære mer om JMH, sjekk ut vår forrige artikkel her.

5. Implementeringsdetaljer

Det er en god mental modell å tenke på en ThreadLocalRandom som en kombinasjon av Trådlokal og Tilfeldig klasser. Faktisk ble denne mentale modellen tilpasset den faktiske implementeringen før Java 8.

Fra og med Java 8, brøt imidlertid denne justeringen helt ut som ThreadLocalRandom ble singleton. Slik gjør du nåværende() metoden ser ut i Java 8+:

statisk endelig forekomst av ThreadLocalRandom = ny ThreadLocalRandom (); public static ThreadLocalRandom current () {if (U.getInt (Thread.currentThread (), PROBE) == 0) localInit (); returinstans; }

Det er sant at du deler en global Tilfeldig eksempel fører til suboptimal ytelse under høy konflikt. Å bruke en dedikert forekomst per tråd er imidlertid også overkill.

I stedet for en dedikert forekomst av Tilfeldig per tråd trenger hver tråd bare å opprettholde sin egen frø verdi. Fra og med Java 8, Tråd klassen selv er ettermontert for å opprettholde frø verdi:

offentlig klasse Trådverktøy Runnable {// utelatt @ jdk.internal.vm.annotation.Contended ("tlr") lang trådLocalRandomSeed; @ jdk.internal.vm.annotation.Contended ("tlr") int threadLocalRandomProbe; @ jdk.internal.vm.annotation.Contended ("tlr") int threadLocalRandomSecondarySeed; }

De threadLocalRandomSeed variabel er ansvarlig for å opprettholde den nåværende frøverdien for ThreadLocalRandom. Videre er det sekundære frøet, threadLocalRandomSecondarySeed, brukes vanligvis internt av slike som ForkJoinPool.

Denne implementeringen inneholder noen få optimaliseringer å gjøre ThreadLocalRandom enda mer performant:

  • Unngå falsk deling ved å bruke @Fornøyd kommentar, som i utgangspunktet legger til nok polstring til å isolere de omstridte variablene i sine egne hurtiglinjelinjer
  • Ved hjelp av sun.misc. usikre for å oppdatere disse tre variablene i stedet for å bruke Reflection API
  • Unngå ekstra hashtable oppslag knyttet til Trådlokal gjennomføring

6. Konklusjon

Denne artikkelen illustrerte forskjellen mellom java.util.Random og java.util.concurrent.ThreadLocalRandom.

Vi så også fordelen med ThreadLocalRandom over Tilfeldig i et flertrådet miljø, samt ytelse og hvordan vi kan generere tilfeldige verdier ved hjelp av klassen.

ThreadLocalRandom er et enkelt tillegg til JDK, men det kan skape en betydelig innvirkning i svært samtidige applikasjoner.

Og som alltid kan implementeringen av alle disse eksemplene finnes på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found