Vejledning til ThreadLocalRandom i Java

1. Oversigt

Generering af tilfældige værdier er en meget almindelig opgave. Dette er grunden til, at Java leverer java.util. tilfældighed klasse.

Denne klasse klarer sig imidlertid ikke godt i et miljø med flere tråde.

På en forenklet måde, årsagen til den dårlige ydeevne af Tilfældig i et miljø med flere tråde skyldes stridigheder - givet at flere tråde deler det samme Tilfældig eksempel.

For at imødegå denne begrænsning, Java introducerede java.util.concurrent.ThreadLocalRandom klasse i JDK 7 - til generering af tilfældige tal i et miljø med flere tråde.

Lad os se hvordan ThreadLocalRandom udfører og hvordan man bruger det i virkelige applikationer.

2. ThreadLocalRandom Over Tilfældig

ThreadLocalRandom er en kombination af Trådlokal og Tilfældig klasser (mere om dette senere) og er isoleret til den aktuelle tråd. Således opnår det bedre ydeevne i et multitrådet miljø ved simpelthen at undgå samtidig adgang til forekomster af Tilfældig.

Det tilfældige tal, der opnås af en tråd, påvirkes ikke af den anden tråd, hvorimod java.util. tilfældighed giver tilfældige tal globalt.

Også i modsætning til Tilfældig,ThreadLocalRandom understøtter ikke indstilling af frø eksplicit. I stedet tilsidesætter den setSeed (langt frø) metode arvet fra Tilfældig at altid smide en Ikke-understøttetOperationException hvis kaldes.

2.1. Tråds strid

Indtil videre har vi fastslået, at Tilfældig klasse klarer sig dårligt i meget samtidige miljøer. For bedre at forstå dette, lad os se, hvordan en af ​​dens primære operationer, næste (int), implementeres:

privat endelig AtomicLong frø; beskyttet int næste (int bits) {lang oldsæd, nextseed; AtomicLong seed = this.seed; gør {oldseed = seed.get (); nextseed = (oldseed * multiplier + addend) & mask; } while (! seed.compareAndSet (oldseed, nextseed)); return (int) (nextseed >>> (48 - bits)); }

Dette er en Java-implementering til Linear Congruential Generator-algoritmen. Det er indlysende, at alle tråde deler det samme frø instansvariabel.

For at generere det næste tilfældige sæt bits forsøger det først at ændre det delte frø værdi atomisk via sammenlignAndSet eller CAS for kort.

Når flere tråde forsøger at opdatere frø samtidig bruger CAS, vinder en tråd og opdaterer frø, og resten taber. At miste tråde vil prøve den samme proces igen og igen, indtil de får en chance for at opdatere værdien og i sidste ende generere tilfældigt tal.

Denne algoritme er låsfri, og forskellige tråde kan udvikle sig samtidigt. Imidlertid, når påstanden er høj, vil antallet af CAS-fejl og gentagne forsøg skade den samlede præstation betydeligt.

På den anden side er ThreadLocalRandom fjerner denne påstand fuldstændigt, da hver tråd har sin egen forekomst af Tilfældig og følgelig sin egen begrænsede frø.

Lad os nu se på nogle af måderne til at generere tilfældig int, lang og dobbelt værdier.

3. Generering af tilfældige værdier ved hjælp af ThreadLocalRandom

I henhold til Oracle-dokumentationen vi skal bare ringe ThreadLocalRandom.current () metode, og den returnerer forekomsten af ThreadLocalRandom for den aktuelle tråd. Vi kan derefter generere tilfældige værdier ved at påkalde tilgængelige instansmetoder i klassen.

Lad os generere en tilfældig int værdi uden grænser:

int ubegrænsetRandomValue = ThreadLocalRandom.current (). nextInt ());

Lad os derefter se, hvordan vi kan generere en tilfældig afgrænsning int værdi, hvilket betyder en værdi mellem en given nedre og øvre grænse.

Her er et eksempel på at generere en tilfældig int værdi mellem 0 og 100:

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

Bemærk, at 0 er den inklusive nedre grænse, og 100 er den eksklusive øvre grænse.

Vi kan generere tilfældige værdier for lang og dobbelt ved at påberåbe sig næsteLang () og næsteDobbelt () metoder på en lignende måde som vist i eksemplerne ovenfor.

Java 8 tilføjer også næsteGaussisk () metode til at generere den næste normalt distribuerede værdi med et gennemsnit på 0,0 og 1,0 standardafvigelse fra generatorens sekvens.

Som med Tilfældig klasse, kan vi også bruge fordobler (), ints () og længes () metoder til at generere strømme af tilfældige værdier.

4. Sammenligning ThreadLocalRandom og Tilfældig Brug af JMH

Lad os se, hvordan vi kan generere tilfældige værdier i et miljø med flere tråde ved at bruge de to klasser og derefter sammenligne deres præstationer ved hjælp af JMH.

Lad os først oprette et eksempel, hvor alle tråde deler en enkelt forekomst af Tilfældig. Her indsender vi opgaven med at generere en tilfældig værdi ved hjælp af Tilfældig eksempel til en ExecutorService:

ExecutorService eksekutor = Executors.newWorkStealingPool (); Liste callables = ny ArrayList (); Tilfældig tilfældig = ny tilfældig (); for (int i = 0; i {return random.nextInt ();}); } executor.invokeAll (callables);

Lad os kontrollere udførelsen af ​​koden ovenfor ved hjælp af JMH benchmarking:

# Kør komplet. Samlet tid: 00:00:36 Benchmark Mode Cnt Score Fejlenheder TrådLocalRandomBenchMarker.randomValuesUsingRandom avgt 20 771.613 ± 222.220 us / op

Lad os ligeledes nu bruge ThreadLocalRandom i stedet for Tilfældig eksempel, der bruger en forekomst af ThreadLocalRandom for hver tråd i poolen:

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

Her er resultatet af brugen TrådLokalRandom:

# Kør komplet. Samlet tid: 00:00:36 Benchmark-tilstand Cnt-score Fejlenheder TrådLocalRandomBenchMarker.randomValuesUsingThreadLocalRandom avgt 20 624.911 ± 113.268 us / op

Endelig ved at sammenligne JMH-resultaterne ovenfor for begge Tilfældig og ThreadLocalRandom, kan vi tydeligt se, at den gennemsnitlige tid, det tager at generere 1000 tilfældige værdier ved hjælp af Tilfældig er 772 mikrosekunder, mens brug ThreadLocalRandom det er omkring 625 mikrosekunder.

Således kan vi konkludere det ThreadLocalRandom er mere effektiv i et meget samtidigt miljø.

For at lære mere om JMH, tjek vores forrige artikel her.

5. Implementeringsoplysninger

Det er en god mental model at tænke på en ThreadLocalRandom som en kombination af Trådlokal og Tilfældig klasser. Faktisk blev denne mentale model tilpasset den faktiske implementering før Java 8.

Fra Java 8 brød denne tilpasning dog fuldstændigt sammen som ThreadLocalRandom blev en singleton. Sådan gør du nuværende() metode ser ud i Java 8+:

statisk endelig ThreadLocalRandom-forekomst = ny ThreadLocalRandom (); offentlig statisk ThreadLocalRandom nuværende () {if (U.getInt (Thread.currentThread (), PROBE) == 0) localInit (); returinstans }

Det er rigtigt, at man deler en global Tilfældig eksempel fører til suboptimal ydeevne under høj anstrengelse. Brug af en dedikeret forekomst pr. Tråd er imidlertid også overkill.

I stedet for en dedikeret forekomst af Tilfældig pr. tråd skal hver tråd kun beholde sin egen frø værdi. Fra og med Java 8 er Tråd Klassen selv er blevet eftermonteret for at vedligeholde frø værdi:

offentlig klasse Trådværktøjer Kan køres {// udeladt @ 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; }

Det threadLocalRandomSeed variabel er ansvarlig for at opretholde den aktuelle frøværdi for ThreadLocalRandom. Desuden er det sekundære frø, threadLocalRandomSecondarySeed, bruges normalt internt af lignende ForkJoinPool.

Denne implementering indeholder et par optimeringer, der skal foretages ThreadLocalRandom endnu mere performant:

  • Undgå falsk deling ved hjælp af @Indhold annotation, som grundlæggende tilføjer tilstrækkelig polstring til at isolere de anfægtede variabler i deres egne cachelinjer
  • Ved brug af sun.misc. usikker for at opdatere disse tre variabler i stedet for at bruge Reflection API
  • Undgå ekstra hashtable opslag tilknyttet Trådlokal implementering

6. Konklusion

Denne artikel illustrerede forskellen mellem java.util. tilfældighed og java.util.concurrent.ThreadLocalRandom.

Vi så også fordelen ved ThreadLocalRandom over Tilfældig i et multitrådet miljø såvel som ydeevne, og hvordan vi kan generere tilfældige værdier ved hjælp af klassen.

ThreadLocalRandom er en simpel tilføjelse til JDK, men det kan skabe en bemærkelsesværdig indvirkning i meget samtidige applikationer.

Og som altid kan implementeringen af ​​alle disse eksempler findes på GitHub.