Uvod u Lock Striping

1. Uvod

U ovom uputstvu naučit ćemo kako postići preciznu sinkronizaciju, poznatu i kao Lock Striping, obrazac za rukovanje istodobnim pristupom podatkovnim strukturama uz održavanje dobrih performansi.

2. Problem

HashMap nije nit podataka sigurna struktura podataka zbog svoje nesinkronizirane prirode. To znači da bi naredbe iz okruženja s više niti mogle rezultirati nedosljednošću podataka.

Da bismo prevladali taj problem, izvornu kartu možemo pretvoriti u Zbirke # synchronizedMap metodu ili koristite HashTable struktura podataka. Oboje će vratiti implementaciju Karta sučelje, ali dolaze po cijenu izvedbe.

Pristup definiranju ekskluzivnog naziva se pristup preko podatkovnih struktura s jednim objektom zaključavanja grubozrna sinkronizacija.

U grubozrnatoj implementaciji sinkronizacije, svaki pristup objektu mora istodobno biti ostvaren po jednoj niti. Na kraju imamo sekvencijalne pristupe.

Cilj nam je omogućiti istodobnim nitima da rade na strukturi podataka, a istovremeno osigurati sigurnost niti.

3. Zaključavanje prugastog

Da bismo postigli svoj cilj, poslužit ćemo se uzorkom Lock Striping. Blokiranje pruga je tehnika kod koje se zaključavanje događa na nekoliko segmenata ili pruga, što znači da se pristupom segmentu zaključava samo taj segment, a ne i cijela struktura podataka.

Postoji nekoliko načina za to:

  • Prvo, mogli bismo koristiti zaključavanje po zadatku, čime maksimiziramo istovremenost među zadacima - ovo ima veći memorijski otisak
  • Ili bismo za svaki zadatak mogli upotrijebiti jedno zaključavanje koje koristi manje memorije, ali i ugrožava performanse u istodobnosti

Kako bi nam pomogao u rješavanju ovog kompromisa performansi i memorije, Guava isporučuje klasu tzv Prugasta. Slično je logici pronađenoj u ConcurrentHashMap, ali Prugasta klasa ide još dalje smanjenjem sinkronizacije različitih zadataka pomoću semafora ili povratnih brava.

4. Brzi primjer

Napravimo brz primjer koji će nam pomoći da shvatimo blagodati ovog uzorka.

Usporedit ćemo HashMap nasuprot ConcurrentHashMap i jedna brava nasuprot prugaste brave što je rezultiralo s četiri eksperimenta.

Za svaki ćemo eksperiment izvoditi istodobna čitanja i pisanja temeljnih podataka Karta. Ono što će se razlikovati je način na koji pristupamo svakoj skupini.

A za to ćemo stvoriti dvije klase - SingleLock i StripedLock. To su konkretne implementacije apstraktne klase ConcurrentAccessExperiment to radi posao.

4.1. Ovisnosti

Budući da ćemo se služiti Guavinim Prugasta klase, dodat ćemo guava ovisnost:

 com.google.guava guava 28,2-jre 

4.2. Glavni proces

Naše ConcurrentAccessExperiment klasa provodi prethodno opisano ponašanje:

javna apstraktna klasa ConcurrentAccessExperiment {javna konačna karta doWork (karta mape, int niti, int slots) {CompletableFuture [] zahtjevi = novi CompletableFuture [niti * slots]; for (int i = 0; i <niti; i ++) {zahtjevi [slots * i + 0] = CompletableFuture.supplyAsync (putSupplier (map, i)); zahtjevi [slots * i + 1] = CompletableFuture.supplyAsync (getSupplier (karta, i)); zahtjevi [slots * i + 2] = CompletableFuture.supplyAsync (getSupplier (karta, i)); zahtjevi [slots * i + 3] = CompletableFuture.supplyAsync (getSupplier (karta, i)); } CompletableFuture.allOf (zahtjevi) .join (); karta povratka; } zaštićeni sažetak Dobavljač putSupplier (karta karte, int ključ); zaštićeni sažetak Dobavljač getSupplier (karta karte, int ključ); }

Važno je napomenuti da smo, budući da je naš test vezan za CPU, ograničili broj segmenata na nekoliko višestrukih dostupnih procesora.

4.3. Istodobni pristup s ReentrantLock

Sada ćemo implementirati metode za naše asinkrone zadatke.

Naše SingleLock klasa definira jedno zaključavanje za cijelu strukturu podataka pomoću a ReentrantLock:

javna klasa SingleLock proširuje ConcurrentAccessExperiment {zaključavanje ReentrantLock; javni SingleLock () {lock = novi ReentrantLock (); } zaštićeni dobavljač putSupplier (karta karte, int ključ) {return (() -> {lock.lock (); pokušajte {return map.put ("ključ" + ključ, "vrijednost" + ključ);} konačno {zaključaj. otključaj ();}}); } zaštićeni dobavljač getSupplier (karta karte, int ključ) {return (() -> {lock.lock (); pokušajte {return map.get ("ključ" + ključ);} konačno {lock.unlock ();}} ); }}

4.4. Istodobni pristup sa Prugasta

Onda StripedLock klasa definira prugastu bravu za svaku kantu:

javna klasa StripedLock proširuje ConcurrentAccessExperiment {Striped lock; javni StripedLock (int kante) {lock = Striped.lock (kante); } zaštićeni dobavljač putSupplier (karta karte, int ključ) {return (() -> {int kanta = ključ% stripedLock.size (); Zaključaj zaključavanje = prugastiLock.get (kanta); lock.lock (); pokušaj {vrati mapu .put ("key" + key, "value" + key);} napokon {lock.unlock ();}}); } zaštićeni dobavljač getSupplier (karta karte, int ključ) {return (() -> {int kanta = ključ% stripedLock.size (); Zaključaj zaključavanje = prugastiLock.get (kanta); lock.lock (); pokušaj {vrati mapu .get ("key" + key);} napokon {lock.unlock ();}}); }}

Dakle, koja strategija ima bolju izvedbu?

5. Rezultati

Upotrijebimo JMH (Java Microbenchmark Harness) kako bismo to saznali. Mjerila se mogu pronaći putem veze izvornog koda na kraju vodiča.

Pokrećući naš benchmark, možemo vidjeti nešto slično sljedećem (imajte na umu da je veća propusnost bolja):

Referentna način CNT rezultat pogrešaka jedinice ConcurrentAccessBenchmark.singleLockConcurrentHashMap thrpt 10 0,059 ± 0,006 OPS / MS ConcurrentAccessBenchmark.singleLockHashMap thrpt 10 0,061 ± 0,005 OPS / MS ConcurrentAccessBenchmark.stripedLockConcurrentHashMap thrpt 10 0,065 ± 0009 OPS / MS ConcurrentAccessBenchmark.stripedLockHashMap thrpt 10 0,068 ± 0,008 OPS / MS 

6. Zaključci

U ovom smo tutorijalu istražili različite načine kako postići bolje performanse koristeći Lock Striping in Karta-poput struktura. Stvorili smo mjerilo za usporedbu rezultata s nekoliko implementacija.

Iz naših referentnih rezultata možemo razumjeti kako bi različite istodobne strategije mogle značajno utjecati na cjelokupni proces. Uzorak prugaste brave daje poprilično poboljšanje jer postiže ~ 10% dodatka s oba HashMap i ConcurrentHashMap.

Kao i obično, izvorni kod za ovu lekciju dostupan je na GitHubu.