Vodič za ThreadLocalRandom na Javi

1. Pregled

Generiranje slučajnih vrijednosti vrlo je čest zadatak. Zbog toga Java nudi java.util.Slučajno razred.

Međutim, ova klasa nema dobru izvedbu u okruženju s više niti.

Na pojednostavljeni način, razlog loših performansi Slučajno u okruženju s više niti dolazi do prepirke - s obzirom na to da više niti dijeli isto Slučajno primjer.

Da bi se riješilo to ograničenje, Java je predstavila java.util.concurrent.ThreadLocalRandom klasa u JDK 7 - za generiranje slučajnih brojeva u okruženju s više niti.

Da vidimo kako ThreadLocalRandom izvodi i kako ga koristiti u stvarnim aplikacijama.

2. ThreadLocalRandom Nad Slučajno

ThreadLocalRandom je kombinacija ThreadLocal i Slučajno klase (o tome više kasnije) i izoliran je za trenutnu nit. Dakle, postiže bolje performanse u višenitnom okruženju jednostavnim izbjegavanjem bilo kakvog istodobnog pristupa instancama Slučajno.

Na slučajni broj koji dobije jedna nit druga nit ne utječe, dok java.util.Slučajno pruža slučajne brojeve globalno.

Također, za razliku od Slučajno,ThreadLocalRandom ne podržava eksplicitno postavljanje sjemena. Umjesto toga, nadjačava setSeed (dugo sjeme) metoda naslijeđena od Slučajno da uvijek baci an UnsupportedOperationException ako se pozove.

2.1. Prepirka u niti

Do sada smo utvrdili da Slučajno klasa ima loš učinak u istodobnim okruženjima. Da bismo to bolje razumjeli, pogledajmo kako je jedna od njegovih primarnih operacija, sljedeći (int), provodi se:

privatno konačno sjeme AtomicLong; zaštićen int next (int bitovi) {dugo staro sjeme, sljedeće sjeme; Sjeme AtomicLong = this.seed; do {oldseed = seed.get (); nextseed = (oldseed * množitelj + dodatak) & mask; } while (! seed.compareAndSet (oldseed, nextseed)); return (int) (nextseed >>> (48 - bitova)); }

Ovo je Java implementacija za algoritam Linearnog kongrucijalnog generatora. Očito je da sve niti dijele isto sjeme varijabla instance.

Da bi generirao sljedeći nasumični skup bitova, prvo pokušava promijeniti zajedničko sjeme vrijednost atomski putem compareAndSet ili CAS za kratko.

Kada više niti pokušava ažurirati sjeme istovremeno koristeći CAS, jedna nit pobjeđuje i ažurira sjeme, a ostali gube. Gubitke niti pokušavat će isti postupak uvijek iznova dok ne dobiju priliku za ažuriranje vrijednosti i u konačnici generirati slučajni broj.

Ovaj algoritam je bez zaključavanja i različite niti mogu istodobno napredovati. Međutim, kada je prepirka velika, broj kvarova i ponovnih pokušaja CAS-a značajno će naštetiti ukupnoj izvedbi.

S druge strane, ThreadLocalRandom u potpunosti uklanja ovu tvrdnju, jer svaka nit ima svoju instancu Slučajno i, shodno tome, vlastito ograničeno sjeme.

Pogledajmo sada neke od načina generiranja slučajnih rezultata int, dugo i dvostruko vrijednosti.

3. Generiranje slučajnih vrijednosti pomoću ThreadLocalRandom

Prema Oracle dokumentaciji, samo trebamo nazvati ThreadLocalRandom.current () i vratit će instancu ThreadLocalRandom za trenutnu nit. Tada možemo generirati slučajne vrijednosti pozivajući se na dostupne metode instance klase.

Generirajmo slučajno int vrijednost bez ikakvih granica:

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

Dalje, pogledajmo kako možemo generirati slučajno ograničeno int vrijednost, što znači vrijednost između zadane donje i gornje granice.

Evo primjera generiranja slučajnog slučaja int vrijednost između 0 i 100:

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

Napominjemo, 0 je uključiva donja granica, a 100 isključiva gornja granica.

Možemo generirati slučajne vrijednosti za dugo i dvostruko prizivanjem nextLong () i nextDouble () metode na sličan način kao što je prikazano u gornjim primjerima.

Java 8 također dodaje nextGaussian () metoda za generiranje sljedeće normalno raspodijeljene vrijednosti s prosjekom 0,0 i 1,0 standardnim odstupanjem od redoslijeda generatora.

Kao i kod Slučajno klase, možemo koristiti i parovi (), intovi () i čezne () metode za generiranje tokova slučajnih vrijednosti.

4. Uspoređivanje ThreadLocalRandom i Slučajno Korištenje JMH

Pogledajmo kako možemo generirati slučajne vrijednosti u okruženju s više niti pomoću dvije klase, a zatim usporediti njihovu izvedbu pomoću JMH.

Prvo, stvorimo primjer gdje sve niti dijele jednu instancu Slučajno. Ovdje podnosimo zadatak generiranja slučajne vrijednosti pomoću Slučajno primjer na Usluga izvršitelja:

ExecutorService izvršitelj = Executors.newWorkStealingPool (); Popis pozivi = novi ArrayList (); Slučajni slučajni = novi Random (); for (int i = 0; i {return random.nextInt ();}); } izvršitelj.invokeAll (pozivi);

Provjerimo izvedbu gornjeg koda koristeći JMH benchmarking:

# Trčanje dovršeno. Ukupno vrijeme: 00:00:36 Benchmark Mode Cnt Rezultat Pogreška Jedinice ThreadLocalRandomBenchMarker.randomValuesUsingRandom prosj. 20 771.613 ± 222.220 us / op

Slično tome, upotrijebimo sada ThreadLocalRandom umjesto Slučajno instanca, koja koristi jedan primjerak ThreadLocalRandom za svaku nit u spremištu:

ExecutorService izvršitelj = Executors.newWorkStealingPool (); Popis pozivi = novi ArrayList (); for (int i = 0; i {return ThreadLocalRandom.current (). nextInt ();}); } izvršitelj.invokeAll (pozivi);

Evo rezultata korištenja ThreadLocalRandom:

# Trčanje dovršeno. Ukupno vrijeme: 00:00:36 Benchmark Mode Cnt Rezultat Pogreška Jedinice ThreadLocalRandomBenchMarker.randomValuesUsingThreadLocalRandom prosj. 20 624.911 ± 113.268 us / op

Konačno, usporedbom gornjih rezultata JMH za oba Slučajno i ThreadLocalRandom, možemo jasno vidjeti da je prosječno vrijeme potrebno za generiranje 1000 slučajnih vrijednosti pomoću Slučajno je 772 mikrosekunde, dok se pomoću ThreadLocalRandom to je oko 625 mikrosekundi.

Stoga to možemo zaključiti ThreadLocalRandom je učinkovitiji u istodobnom okruženju.

Da biste saznali više o JMH, pogledajte naš prethodni članak ovdje.

5. Detalji provedbe

Dobar je mentalni model za razmišljanje o ThreadLocalRandom kao kombinacija ThreadLocal i Slučajno razreda. Zapravo, ovaj mentalni model bio je usklađen sa stvarnom implementacijom prije Jave 8.

Međutim, od Jave 8 ovo se poravnanje potpuno pokvarilo kao ThreadLocalRandom postao samac. Evo kako Trenutno() metoda izgleda u Javi 8+:

statička konačna instanca ThreadLocalRandom = nova ThreadLocalRandom (); javna statička struja ThreadLocalRandom () {if (U.getInt (Thread.currentThread (), PROBE) == 0) localInit (); povratna instanca; }

Istina je da dijeljenje jednog globalnog Slučajno instanca dovodi do neoptimalnih performansi u visokoj konkurenciji. Međutim, korištenje jedne namjenske instance po niti također je pretjerano.

Umjesto namjenske instance Slučajno po niti, svaka nit samo treba održavati svoju sjeme vrijednost. Od Jave 8, Nit sama klasa je naknadno ugrađena u svrhu održavanja sjeme vrijednost:

javna klasa Thread implementira Runnable {// izostavljen @ jdk.internal.vm.annotation.Contended ("tlr") long threadLocalRandomSeed; @ jdk.internal.vm.annotation.Contended ("tlr") int threadLocalRandomProbe; @ jdk.internal.vm.annotation.Contended ("tlr") int threadLocalRandomSecondarySeed; }

The threadLocalRandomSeed varijabla odgovorna je za održavanje trenutne vrijednosti sjemena za ThreadLocalRandom. Štoviše, sekundarno sjeme, threadLocalRandomSecondarySeed, obično ga koriste osobe poput ForkJoinPool.

Ova implementacija uključuje nekoliko optimizacija ThreadLocalRandom još učinkovitiji:

  • Izbjegavanje lažnog dijeljenja pomoću @Zadovoljan napomena, koja u osnovi dodaje dovoljno dodataka za izoliranje spornih varijabli u vlastitim linijama predmemorije
  • Koristeći sunce.misc.Nesigurno za ažuriranje ove tri varijable umjesto korištenja Reflection API-ja
  • Izbjegavanje dodatnih pretraživanja raspršivanjem povezanih s ThreadLocal provedba

6. Zaključak

Ovaj je članak ilustrirao razliku između java.util.Slučajno i java.util.concurrent.ThreadLocalRandom.

Vidjeli smo i prednost ThreadLocalRandom nad Slučajno u višenitnom okruženju, kao i performanse i kako možemo generirati slučajne vrijednosti pomoću klase.

ThreadLocalRandom je jednostavan dodatak JDK-u, ali može stvoriti značajan utjecaj u istodobnim aplikacijama.

I kao i uvijek, provedba svih ovih primjera može se naći na GitHubu.


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