Java konkurentno programiranje — obavezna štiva za intervju, verzija za napametovanje🍉
Ukratko o Java memorij modelu (JMM)
Java memorij model definiše pravila pristupa različitim promenljivama u programu:
- Sve promenljive se čuvaju u glavnoj memoriji, a svaka nit ima svoju radnu memoriju.
- Radna memorija čuva kopije promenljivih iz glavne memorije koje ta nit koristi; sve operacije niti nad promenljivama moraju se obavljati u radnom prostoru i ne mogu neposredno čitati ili pisati podatke iz glavne memorije.
- Po završetku operacije, radna memorija niti putem protokola koherentnosti keša (cache coherence) piše izmenjene podatke nazad u glavnu memoriju.
Ukratko o as-if-serial
Kompajler vrši preuređivanje instrukcija i optimizaciju originalnog programa. Ali bez obzira na preuređivanje, rezultat mora ostati u skladu sa unapred određenim rezultatom koji korisnikov originalni program daje.
Ukratko o osam happens-before pravila
- Pravilo redosleda programa: unutar jedne niti, po redosledu koda, operacija napisana ispred dešava-se-pre operacije napisane iza;
- Pravilo zaključavanja: operacija unLock dešava-se-pre kasnijoj operaciji lock nad istim brodom (lock);
- Pravilo volatile promenljive: operacija pisanja nad nekom promenljivom dešava-se-pre kasnijoj operaciji čitanja te promenljive;
- Pravilo tranzitivnosti: ako operacija A se dešava-pre operacije B, a operacija B se dešava-pre operacije C, onda operacija A se dešava-pre operacije C;
- Pravilo pokretanja niti: metod start() objekta Thread dešava-se pre svake akcije te niti;
- Pravilo prekida niti: poziv metoda interrupt() niti dešava-se pre nego što kôd prekinute niti detektuje nastanak događaja prekida;
- Pravilo završetka niti: sve operacije u niti dešavaju-se pre provere završetka niti; završetak se može otkriti završetkom metoda Thread.join() ili povratnom vrednošću Thread.isAlive();
- Pravilo finalizacije objekta: dovršetak inicijalizacije objekta dešava-se pre početka njegovog metoda finalize();
Razlika između as-if-serial i happens-before
as-if-serial garantuje da rezultat izvršavanja jednonitnog programa ostane nepromenjen, dok happens-before garantuje da rezultat izvršavanja pravilno sinhronizovanog višenitnog programa ostane nepromenjen.
Ukratko o atomarnim operacijama
Jedna operacija ili više operacija — ili se izvrše sve i proces izvršavanja ne prekida nijedan faktor, ili se ne izvrši nijedna; to je atomarna operacija.
Ukratko o vidljivosti između niti
Vidljivost znači da kada jedna nit izmeni deljenu promenljivu, ostale niti mogu odmah da saznaju za izmenu. Ključne reči volatile, synchronized i final sve garantuju vidljivost.
Ukratko o uređenosti
Iako kod višenitnog rada postoje konkurentnost i optimizacija instrukcija i sl., posmatrano iz same niti sve njene operacije izvršavanja su uređene.
Ukratko o ulozi ključne reči volatile u Javi
- Garantuje vidljivost promenljive za sve niti. Kada jedna nit izmeni vrednost promenljive, nova vrednost ostalim nitima postaje odmah poznata.
- Zabranjuje preuređivanje instrukcija. Pri pisanju u volatile promenljivu, kompajler prilikom generisanja bajtkoda u niz instrukcija umeće memorijske barijere kojima se zabranjuje preuređivanje određenim tipovima procesora.
Načini realizacije niti u Javi
- implementacija interfejsa Runnable
- nasleđivanje klase Thread
- implementacija interfejsa Callable
Ukratko o stanjima niti u Javi
Stanja niti su NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED.
- NEW: stanje novo Kreirane niti; nit je kreirana ali nije pokrenuta, još uvek nije pozvan metod start.
- RUNNABLE: stanje izvršavanja. Označava da se nit izvršava u JVM-u, ali to izvršavanje ne mora da znači da stvarno radi — možda čeka u redu na CPU.
- BLOCKED: stanje blokade. Nit čeka da dobije brod (lock), koji još uvek nije dobijen.
- WAITING: stanje čekanja. Nit ulazi u ovo stanje kada unutar metoda run pozove Object.wait()/Thread.join().
- TIMED_WAITING: čekanje sa vremenskim ograničenjem. Napušta se nakon određenog vremena. U stanje se ulazi pozivom Thread.sleep(long), Object.wait(long), Thread.join(long), gde parametar predstavlja vreme čekanja.
- TERMINATED: stanje završetka. Nit ulazi u ovo stanje nakon što se izvrši metod run.
Ukratko o načinima komunikacije između niti
- volatile ključna reč kojom se modifikuje promenljiva, čime se garantuje vidljivost pristupa toj promenljivoj svim nitima.
- synchronized ključna reč. Omogućava da u istom trenutku samo jedna nit bude u metodi ili sinhronizovanom bloku.
- metode wait/notify
- IO komunikacija
Ukratko o bazenu niti (thread pool)
Bez bazena niti, višestruko kreiranje i uništavanje niti izaziva veliki trošak. Ako se niti kreirane da izvrše trenutni zadatak, nakon završetka, ponovo koriste, može se smanjiti trošak i kontrolisati maksimalan broj istovremenih niti.
Kada bazen niti kreira nit, ona se obmotava u radnu nit (Worker); Worker, nakon što izvrši zadatak, i dalje u petlji uzima zadatke iz radnog reda i izvršava ih.
Pri slanju zadatka na bazen niti javljaju se sledeće situacije:
- Ako jezgro bazena niti nije puno, kreira se nova nit koja izvršava zadatak.
- Ako je jezgro puno, a radni red nije, nit se smešta u radni red.
- Ako je radni red pun, a broj niti je manji od maksimalnog, kreira se nova nit koja obrađuje zadatak.
- Ako se premaši maksimalan broj niti, zadatak se obrađuje po strategiji odbijanja.
Parametri bazena niti:
- corePoolSize: broj stalnih niti jezgra. Ako broj premaši ovu vrednost, a niti su u mirovanju, biće uništene.
- maximumPoolSize: maksimalan broj niti koje bazen može istovremeno da primi na izvršavanje.
- keepAliveTime: vreme mirovanja niti; kada mirovanje niti dostigne ovu vrednost, nit se uništava, sve dok ne ostanu samo corePoolSize niti, čime se izbegava rasipanje memorijskih resursa.
- workQueue: radni red.
- threadFactory: fabrika niti, koja proizvodi skup niti za iste zadatke.
- handler: strategija odbijanja.
Postoji nekoliko strategija odbijanja:
- AbortPolicy: odbacuje zadatak i baca izuzetak.
- CallerRunsPolicy: ponovo pokušava da preda taj zadatak.
- DiscardOldestPolicy: odbacuje zadatak koji najduže čeka u redu i dodaje trenutni zadatak u red.
- DiscardPolicy: direktno odbacuje trenutni zadatak, ali ne baca izuzetak.
Ukratko o Executor okviru
Executor okvir ima za cilj da razdvoji predaju zadatka od načina na koji se zadatak izvršava. Korisnik više ne mora na nivou koda da projektuje predaju i izvršavanje zadatka; dovoljno je da pozove metod Execute klase koja realizuje Executor okvir i time predaje zadatak.
Ukratko o hijerarhiji Executor okvira
- Executor: interfejs koji definiše metod executor koji prima Runnable objekat; taj metod prima jednu Runnable instancu i izvršava taj zadatak.
- ExecutorService: podinterfejs Executor-a; definiše metod koji prima Callable objekat i vraća Future objekat, a pruža i metod execute.
- ScheduledExecutorService: podinterfejs ExecutorService-a; podržava periodično izvršavanje zadataka.
- AbstractExecutorService: apstraktna klasa koja pruža podrazumevanu implementaciju metoda izvršavanja iz ExecutorService-a.
- Executors: statička fabrička klasa koja implementira interfejs ExecutorService; pruža niz fabričkih metoda za kreiranje bazena niti.
- ThreadPoolExecutor: nasleđuje AbstractExecutorService; služi za kreiranje bazena niti.
- ForkJoinPool: nasleđuje AbstractExecutorService; Fork raščlanjuje veliki zadatak na više malih, pa se mali zadaci izvršavaju, a Join prikuplja rezultate malih zadataka — slično map-reduce.
- ScheduledThreadPoolExecutor: nasleđuje ThreadPoolExecutor i implementira ScheduledExecutorService; služi za kreiranje bazena niti sa zadacima na tajmer.
Ukratko o stanjima bazena niti
- Running: prihvata nove predate zadatke i obrađuje i zadatke iz blokirajućeg reda.
- Shutdown: više ne prihvata nove zadatke, ali obrađuje postojeće; u stanje se ulazi pozivom metoda shutdown dok je bazen u stanju Running.
- Stop: ne prihvata nove zadatke i ne obrađuje postojeće; u stanje se ulazi pozivom shutdownNow.
- Tidying: svi zadaci su završeni, worker_count (broj aktivnih niti) je 0.
- Terminated: bazen niti je potpuno završen. U stanje se ulazi pozivom metoda terminated u režimu Tidying.
Ukratko o tipovima bazena niti
- newCachedThreadPool: kesirajući bazen niti; moguće je podesiti minimalan i maksimalan broj niti; nit u mirovanju se automatski uništava nakon 1 minuta.
- newFixedThreadPool: bazen niti sa zadatim brojem radnih niti.
- newSingleThreadExecutor: jednonitni Executor.
- newScheduleThreadPool: bazen niti sa zadatim brojem radnih niti koji podržava zadatke na tajmer.
- newSingleThreadScheduledExecutor: jednonitni Executor koji podržava zadatke na tajmer.
Ukratko o blokirajućem redu
Blokirajući red jedan je od konkretnih mehanizama koji realizuje odnos proizvođač-potrošač. Kada je blokirajući red prazan, operacija uzimanja elementa iz reda biće blokirana; kada je blokirajući red pun, operacija dodavanja elementa u red biće blokirana. Konkretne realizacije su:
- ArrayBlockingQueue: ograaničeni blokirajući red zasnovan na nizu.
- LinkedBlockingQueue: ograaničeni blokirajući red zasnovan na povezanoj listi.
- PriorityBlockingQueue: blokirajući red sa prioritetom.
- DelayQueue: pri kreiranju elementa može se navesti koliko vremena treba da prođe dok se taj element ne može uzeti iz reda.
- SynchronousQueue: blokirajući red koji ne čuva elemente; svako ubacivanje mora sačekati odgovarajuće izbacivanje.
- LinkedTransferQueue: u poređenju sa LinkedBlockingQueue ima dodatni metod transfer — ako trenutno postoji potrošač koji čeka na prijem elementa, element koji je prosledio proizvođač može mu se odmah preneti.
- LinkedBlockingDeque: dvosmerni blokirajući red.
Nešto o ThreadLocal
ThreadLocal je klasa za promenljive privatne za nit. ThreadLocal ima statičku unutrašnju klasu ThreadLocalMap čiji je ključ ThreadLocal objekat, a vrednost Entry objekat; ThreadLocalMap je privatan za svaku nit.
- set: postavlja vrednost u ThreadLocalMap.
- get: pribavlja vrednost iz ThreadLocalMap.
- remove: uklanja ThreadLocalMap objekat.
Postojeći problem: u bazenu niti, pošto bazen niti ponovo koristi Thread objekte, i ThreadLocal vezan za Thread biće ponovo korišćen, što izaziva niz problema.
Na primer, curenje memorije. Pošto je ThreadLocal slaba referenca, ali je value u Entry jaka referenca, kada ThreadLocal bude prikupljen kao otpad, value i dalje neće biti oslobođena, što stvara curenje memorije.
Šta mislite o klasi Unsafe iz Java konkurentnog paketa
Za jezik Java: nema direktne komponente pokazivača, i generalno se ne može operisati nad nekim delom memorije pomoću ofseta. Te su operacije relativno bezbedne (safe).
Java ima klasu koja se zove Unsafe; ona Javi daje sposobnost da, poput pokazivača u C-u, operiše nad memorijskim prostorom, ali sa sobom donosi i probleme pokazivača. Ta klasa se može smatrati temeljem Java konkurentnog razvoja.
Optimističko zaključavanje i CAS algoritam u Javi
Optimističko zaključavanje smatra da verovatnoća konkurentnog sukoba pri slanju podataka nije velika, pa se pre operacije čitanja ne zaključava.
Tek pri operaciji pisanja se proverava da li su podatke u međuvremenu izmenile druge niti. Ako je izmena nastupila, vraća se neuspeh pisanja; ako nije, izvršava se izmena i vraća se uspeh.
Optimističko zaključavanje se uglavnom realizuje algoritmom Compare And Swap (CAS). Kao što ime kaže, taj algoritam obuhvata dve operacije: poređenje (Compare) i zamenu (Swap).
Ideja CAS algoritma je sledeća:
- Smatra se da je konkurencija pri radu različitih niti nad istom promenljivom retka.
- Srž algoritma je poređenje trenutno pročitane vrednosti promenljive E sa starom vrednošću promenljive u memoriji V.
- Ako su jednake, to znači da druge niti nisu menjale tu promenljivu, pa se vrednost ažurira na novu vrednost N.
- Ako nisu jednake, smatra se da su u fazi od čitanja vrednosti E do poređenja druge niti menjale promenljivu, pa se ne preduzima ništa.
Ukratko o ABA problemu i rešenju
CAS algoritam poredi po vrednosti. Ako postoje dve niti, jedna promeni vrednost promenljive iz A u B, pa je iz B vrati u A, kada trenutna nit počne da izvršava CAS lako će zaključiti da se vrednost nije promenila i pogrešno pomisliti da u intervalu od čitanja podataka do izvršavanja CAS-a nijedna nit nije menjala podatke.
Paket juc (java.util.concurrent) pruža AtomicStampedReference, koji originalnoj verziji dodaje žig sa brojem verzije i time rešava ABA problem.
Ukratko o uobičajenim Atomic klasama
U mnogim slučajevima potrebna nam je samo prosta, efikasna i bezbedna za nite operacija ++ ili --; može se realizovati ključnom reči synchronized i brodom (lock), ali je cena velika, pa su tada atomske klase praktičnije. Atomske klase osnovnih tipova podataka su:
- AtomicInteger: atomarno ažurira ceo broj (int).
- AtomicLong: atomarno ažurira dugački ceo broj (long).
- AtomicBoolean: atomarno ažurira logički tip (boolean).
Atomske klase nizova su:
- AtomicIntegerArray: atomarno ažurira elemente niza celih brojeva.
- AtomicLongArray: atomarno ažurira elemente niza dugačkih celih brojeva.
- AtomicReferenceArray: atomarno ažurira elemente niza referentnih tipova.
Atomske klase referentnih tipova su:
- AtomicReference: atomarno ažurira referentni tip.
- AtomicMarkableReference: atomarno ažurira referentni tip sa oznakom; može se vezati jedna boolean oznaka.
- AtomicStampedReference: atomarno ažurira referentni tip sa brojem verzije.
Klase FieldUpdater:
- AtomicIntegerFieldUpdater: ažurirač koji atomarno ažurira polja celog broja.
- AtomicLongFieldUpdater: ažurirač koji atomarno ažurira polja dugačkih celih brojeva.
- AtomicReferenceFieldUpdater: ažurirač koji atomarno ažurira polja referentnog tipa.
Ukratko o osnovnom principu realizacije Atomic klasa
Primer: AtomicInteger.
Metod getAndIncrement na atomaran način uvećava trenutnu vrednost za 1; konkretna realizacija je:
- u beskonačnoj for petlji pribavi se vrednost sačuvana u AtomicInteger-u;
- trenutna vrednost AtomicInteger-a se uveća za 1;
- pozove se metod compareAndSet za atomarno ažuriranje;
- najpre se proveri da li je trenutna vrednost jednaka expect;
- ako jeste, to znači da trenutnu vrednost nisu izmenile druge niti, pa se vrednost ažurira na next;
- ako nije, ažuriranje ne uspe i vrati false, a program ulazi u for petlju i ponovo izvršava compareAndSet.
Ukratko o CountDownLatch
Klasa CountDownLatch omogućava jednoj niti da sačeka da sve ostale niti završe svoje izvršavanje, pa tek onda da se izvrši. Realizuje se preko brojača čija je početna vrednost jednaka broju niti. Kad jedna nit završi, poziva metod countDown, pa se brojač smanji za 1; kada brojač dođe do 0, to znači da su sve niti završile, i niti koje čekaju mogu da nastave s radom. Može se koristiti samo jednom i ne može se resetovati.
Ukratko o CyclicBarrier
Glavna funkcija CyclicBarrier-a slična je CountDownLatch-u — i on preko brojača omogućava jednoj niti da sačeka da ostale niti završe pre nego što se izvrši. Razlika je što se može koristiti više puta (reset).
Ukratko o Semaphore
Semaphore je semafor. Konstruktor Semaphore-a prima jedan int i time postavlja brojač koji predstavlja broj dostupnih dozvola, odnosno maksimalan broj istovremenih niti. Metodom acquire dobija se jedna dozvola, pa se brojač smanji za jedan; metodom release dozvola se vraća, pa se brojač uveća za jedan. Ako je u tom trenutku brojač 0, nit prelazi u mirovanje.
Ukratko o Exchanger
Klasa Exchanger može da se koristi za razmenu informacija između dve niti. Exchanger objekat se prosto može shvatiti kao posuda sa dva pregradka; metodom exchange u oba pregradka mogu se sipati informacije. Niti razmenjuju podatke metodom exchange: prva nit, nakon što pozove exchange, blokira i čeka da i druga nit pozove taj metod. Kada obe niti stignu do tačke sinhronizacije, mogu da razmene podatke — kada su oba pregradka popunjena, taj objekat automatski razmeni njihov sadržaj i vrati ga nitima, čime se ostvaruje razmena informacija između dve niti.
Ukratko o ConcurrentHashMap
JDK7 koristi tehniku segmentnog zaključavanja: prvo se podaci podele na Segment segmente, a zatim se svakom segmentu dodeli po jedan brod (lock); kada jedna nit zauzme brod i pristupa podacima jednog segmenta, podacima ostalih segmenata mogu pristupati i druge niti.
get, osim kada pročita null, ne zahteva zaključavanje. Metod prvo prolazi kroz jedno ponovno heširanje, zatim tom heš vrednošću pomoću heš operacije locira Segment, i konačno heš algoritmom locira element. put mora da se zaključa: prvo se locira Segment, a zatim se vrši umetanje — prvi korak je provera da li je potrebno proširiti niz HashEntry u tom Segmentu, a drugi korak je lociranje mesta na koje se element dodaje, pa se on smešta u niz.
Poboljšanja u JDK8:
- ukida se mehanizam segmentnog zaključavanja i usvaja CAS algoritam za postavljanje vrednosti; ako CAS ne uspe, dodavanje elementa se radi pod synchronized zaključavanjem;
- uvodi se struktura crveno-crnog stabla; kada broj elemenata u nekom slotu pređe 8, a kapacitet Node niza je veći od 64, povezana lista se pretvara u crveno-crno stablo.
- koristi se optimizovaniji način prebrojavanja elemenata u skupu.
Princip realizacije synchronized na donjem nivou
Svaki Java objekat je na donjem nivou povezan sa jednim monitorom. Pri korišćenju synchronized, JVM u skladu sa okruženjem pronalazi monitor objekta i na osnovu stanja monitora odlučuje o zaključavanju i otključavanju. Ako zaključavanje uspe, ta nit postaje jedini vlasnik monitora, i dok se monitor ne oslobodi, nijedna druga nit ga ne može zauzeti.
synchronized se, nakon kompajliranja u JVM, prevodi u dve instrukcije bajtkoda — monitorenter i monitorexit — kojima se monitor zauzima i oslobađa. Obje instrukcije zahtevaju jedan parametar referentnog tipa kojim se navodi objekat koji se zaključava ili otključava: za sinhronizovanu običnu metodu, brod je trenutna instanca objekta; za sinhronizovanu statičku metodu, brod je Class objekat trenutne klase; za sinhronizovani blok metode, brod je objekat u zagradama synchronized.
Pri izvršavanju instrukcije monitorenter najpre se pokušava zauzeti brod objekta. Ako taj objekat nije zaključan, ili ga trenutna nit već drži, brojač broda se uveća za 1; pri izvršavanju monitorexit brojač broda se smanji za 1. Čim brojač postane 0, brod se odmah oslobađa.
Načini upotrebe ključne reči synchronized
- direktna modifikacija neke metode instance;
- direktna modifikacija neke statičke metode;
- modifikacija bloka koda.
Ukratko o pristrasnom brodu (biased lock) u Javi
U JDK 1.6 uveden je pojam pristrasnog broda (biased lock). Razlog uvođenja jeste što su programeri otkrili da u većini slučajeva ne postoji konkurencija oko broda — jedan brod najčešće dobija uvek ista nit. Pristrasni brod se ne oslobađa aktivno, pa pri svakom ulasku pristrasnog broda samo se proverava da li je resurs pristrasen ka toj niti; ako jeste, nisu potrebne dodatne operacije i može se odmah ući u sinhronizovanu operaciju.
Tok zahteva je sledeći:
- Najpre se proverava da li Mark Word objekta pripada pristrasnom režimu; ako ne pripada, ulazi se u logiku provere lakeg broda, inače se ide na sledeći korak;
- proverava se da li je ID niti koja traži brod isti kao ID niti zabeležen u samom pristrasnom brodu. Ako jeste, ide se na sledeći korak; ako nije, skače se na korak 4;
- proverava se da li je potrebno ponovno pristrasavanje; ako nije, direktno se dobija pristrasni brod;
- pomoću CAS algoritma menja se Mark Word objekta tako da deo ID-ja niti postane ID te niti. Ako zamena uspe, ponovno pristrasavanje je završeno i dobija se pristrasni brod; ako ne uspe, to znači da postoji višenitna konkurencija, pa se brod nadograđuje u laki brod.
Ukratko o lakeg brodu (lightweight lock)
Laki brod služi da, kada nema konkurencije, smanji pad performansi koji bi nastao pojavom teškog broda.
Tok zahteva je sledeći:
- Ako sinhronizovani objekat nije zaključan, virtuelna mašina u okviru steka (stack frame) trenutne niti gradi prostor za zapis broda u koji smešta kopiju trenutnog Mark Word objekta broda.
- Virtuelna mašina pokušava CAS-om da ažurira Mark Word objekta tako da ukazuje na pokazivač zapisa broda;
- ako ažuriranje uspe, ta nit je dobila brod, a bit oznake broda prelazi u 00, što označava stanje lakog zaključavanja.
- Ako ažuriranje ne uspe, to znači da bar jedna nit konkurise trenutnoj. Virtuelna mašina proverava da li Mark Word objekta ukazuje na okvir steka trenutne niti;
- ako ukazuje na okvir steka trenutne niti, to znači da trenutna nit već drži brod, pa se izravno ulazi u sinhronizovani blok i nastavlja izvršavanje;
- ako ne, to znači da je objekat broda već zauzela druga nit.
- Ako se dve ili više niti bore za isti brod, laki brod više ne važi i širi se u teški brod; stanje oznake broda postaje 10, Mark Word tada čuva pokazivač na teški brod, a niti koje čekaju na brod moraju da se blokiraju.
Ukratko o strategijama optimizacije broda (lock)
To su strategije poput adaptivnog spina, uklanjanja broda, zadebljanja broda i nadogradnje broda.
Ukratko o spin-brodu (spin lock) u Javi
Kad nit ne uspe da dobije brod, može primeniti ovakvu strategiju: ne prepušta CPU, već u krug ponovo pokušava — ta se operacija naziva spin-brod.
Ukratko o adaptivnom spin-brodu
Kod adaptivnog spin-broda broj spinova se više ne postavlja ručno; obično ga određuju vreme spinovanja pri prethodnom pokušaju na istom brodu i stanje vlasnika broda.
Ukratko o zadebljanju broda (lock coarsening)
Ideja zadebljanja broda je proširivanje obima zaključavanja, čime se izbegava stalno zaključavanje i otključavanje.
Ukratko o uklanjanju broda (lock elimination)
Uklanjanje broda je još temeljnija optimizacija: pri kompajliranju Java kompajler pregleda kontekst izvršavanja i uklanja brodove kod kojih ne može postojati konkurencija za deljene resurse.
Ukratko o Lock-u i ReentrantLock-u
Interfejs Lock je interfejs na vrhu Java konkurentnog paketa.
ReentrantLock, ulazni (reentrant) brod, najčešća je realizacija Lock-a i, kao synchronized, omogućava ponovni ulazak. Podrazumevano ReentrantLock nije pravedan; konstruktorom se može zatražiti pravedan brod. Kada se jednom koristi pravedan brod, performanse padaju.
Ukratko o AQS-u
AQS (AbstractQueuedSynchronizer) je apstraktni sinhronizator zasnovan na redu. AQS svaku nit koja traži deljeni resurs obmotava u jedan čvor (Node) reda za brod, čime se ostvaruje dodela broda. AQS je osnovni okvir za izgradnju broda ili drugih komponenti sinhronizacije; kao deljeni resurs koristi jednu promenljivu volatile int state. Ako nit ne uspe da pribavi resurs, ulazi u sinhroni red i čeka; ako uspe, izvršava kôd kritične sekcije, a pri oslobađanju resursa obaveštava niti koje čekaju u sinhronom redu.
Podklase menjaju stanje sinhronizacije nasleđivanjem sinhronizatora i implementacijom njegovih apstraktnih metoda getState, setState i compareAndSetState.
Princip AQS-a pri dobijanju/oslobađanju isključivog broda:
Dobijanje (acquire):
- Poziva se metod tryAcquire kojim se bezbedno pribavlja stanje sinhronizacije niti; niti koje ne uspeju konstruišu se kao čvor sinhronizacije i metodom addWaiter dodaju na rep sinhronog reda, gde spinuju.
- Poziva se metod acquireQueued kojim taj čvor u beskonačnoj petlji pokušava da pribavi stanje sinhronizacije; ako ne uspe, blokira se.
Oslobađanje (release):
- Poziva se metod tryRelease kojim se oslobađa stanje sinhronizacije;
- poziva se metod unparkSuccessor koji budi nasledni čvor čvora glave, pa nasledni čvor ponovo pokušava da pribavi stanje sinhronizacije.
Princip AQS-a pri dobijanju/oslobađanju deljenog broda:
Dobijanje broda (acquireShared):
- Poziva se metod tryAcquireShared kojim se pokušava pribavljanje stanja sinhronizacije; povratna vrednost ne manja od 0 znači da je stanje sinhronizacije dobijeno.
- Oslobađanje (releaseShared) i buđenje narednih čvorova koji su u stanju čekanja.
Autor priloga: Houduan Jishu Xiaoniu Shuo Izvorni link: https://mp.weixin.qq.com/s/PmeH38qWVxyIhBpsAsjG7w
