Dotazy na rozhovor so súbežnosťou Java (+ odpovede)

Tento článok je súčasťou série: • Otázky týkajúce sa rozhovorov o zbierkach Java

• Dotazy týkajúce sa systému typu Java

• Dotazy k súbežnému rozhovoru Java (+ odpovede) (aktuálny článok) • Dotaz na rozhovor o štruktúre triedy Java a inicializácii

• Otázky týkajúce sa rozhovoru s Java 8 (+ odpovede)

• Správa pamäte v otázkach týkajúcich sa rozhovorov Java (+ odpovede)

• Java Generics Interview otázky (+ odpovede)

• Otázky týkajúce sa riadenia toku Java (+ odpovede)

• Otázky týkajúce sa rozhovorov o výnimkách jazyka Java (+ odpovede)

• Dotazy k anotáciám Java (+ odpovede)

• Najlepšie otázky týkajúce sa jarného rámcového rozhovoru

1. Úvod

Súbežnosť v Jave je jednou z najkomplexnejších a najpokročilejších tém vyvolaných počas technických rozhovorov. Tento článok poskytuje odpovede na niektoré otázky z pohovoru týkajúce sa témy, s ktorými sa môžete stretnúť.

Q1. Aký je rozdiel medzi procesom a vláknom?

Procesy aj vlákna sú jednotkou súbežnosti, majú však zásadný rozdiel: procesy nezdieľajú spoločnú pamäť, zatiaľ čo vlákna áno.

Z hľadiska operačného systému je proces nezávislý softvér, ktorý beží vo vlastnom priestore virtuálnej pamäte. Akýkoľvek multitaskingový operačný systém (čo znamená takmer akýkoľvek moderný operačný systém) musí oddeľovať procesy v pamäti, aby jeden zlyhávajúci proces nestiahol všetky ostatné procesy nadol zakódovaním spoločnej pamäte.

Procesy sú tak zvyčajne izolované a spolupracujú prostredníctvom medziprocesovej komunikácie, ktorú operačný systém definuje ako akýsi medziproces API.

Naopak, vlákno je časť aplikácie, ktorá zdieľa spoločnú pamäť s ostatnými vláknami tej istej aplikácie. Používanie spoločnej pamäte umožňuje oholiť veľa réžie, navrhnúť vlákna tak, aby spolupracovali a vymieňali si medzi nimi údaje oveľa rýchlejšie.

Q2. Ako môžete vytvoriť inštanciu vlákna a spustiť ju?

Ak chcete vytvoriť inštanciu vlákna, máte dve možnosti. Najskôr odovzdajte a Spustiteľné inštancia jeho konštruktoru a volať štart (). Spustiteľné je funkčné rozhranie, takže ho možno odovzdať ako výraz lambda:

Thread thread1 = new Thread (() -> System.out.println ("Hello World from Runnable!")); thread1.start ();

Niť tiež vykonáva Spustiteľné, takže ďalším spôsobom spustenia vlákna je vytvorenie anonymnej podtriedy a jej prepísanie run () metódou a potom zavolajte štart ():

Thread thread2 = new Thread () {@Override public void run () {System.out.println ("Hello World from subclass!"); }}; thread2.start ();

Q3. Popíšte rôzne stavy vlákna a kedy nastávajú štátne prechody.

Štát a Závit možno skontrolovať pomocou Thread.getState () metóda. Rôzne stavy a Závit sú opísané v Závit. Štát enum. Oni sú:

  • NOVÝ - nový Závit inštancia, ktorá ešte nebola spustená prostredníctvom Thread.start ()
  • BEŽNÉ - bežiace vlákno. Nazýva sa spustiteľný, pretože v ktoromkoľvek okamihu môže byť v prevádzke alebo môže čakať na ďalšie kvantum času z plánovača vlákien. A NOVÝ vlákno vstupuje do BEŽNÉ uveďte, kedy voláte Thread.start () na to
  • ZABLOKOVANÉ - bežiace vlákno sa zablokuje, ak potrebuje vstúpiť do synchronizovanej sekcie, ale nemôže to urobiť kvôli inému vláknu, ktoré drží monitor tejto sekcie
  • ČAKANIE - vlákno vstúpi do tohto stavu, ak čaká na vykonanie konkrétnej akcie iným vláknom. Napríklad vlákno vstúpi do tohto stavu po zavolaní Object.wait () metóda na monitore, ktorý drží, alebo Thread.join () metóda na inom vlákne
  • TIMED_WAITING - rovnaké ako vyššie, ale vlákno vstúpi do tohto stavu po vyvolaní časovaných verzií Thread.sleep (), Object.wait (), Thread.join () a niektoré ďalšie metódy
  • UKONČENÝ - vlákno dokončilo vykonávanie svojho - Runnable.run () metóda a ukončená

Q4. Aký je rozdiel medzi spustiteľnými a volanými rozhraniami? Ako sa používajú?

The Spustiteľné rozhranie má jeden bežať metóda. Predstavuje výpočtovú jednotku, ktorá musí byť spustená v samostatnom vlákne. The Spustiteľné rozhranie neumožňuje tejto metóde vrátiť hodnotu alebo vyvolať nekontrolované výnimky.

The Vyvolávateľná rozhranie má jeden hovor metóda a predstavuje úlohu, ktorá má hodnotu. Preto hovor metóda vráti hodnotu. Môže to spôsobiť aj výnimky. Vyvolávateľná sa všeobecne používa v ExecutorService inštancie na spustenie asynchrónnej úlohy a potom volanie vráteného Budúcnosť napríklad získať jeho hodnotu.

Q5. Čo je vlákno daemon, aké sú jeho prípady použitia? Ako môžete vytvoriť vlákno démona?

Vlákno démona je vlákno, ktoré nebráni spusteniu JVM. Keď sú ukončené všetky vlákna, ktoré nie sú démonmi, JVM jednoducho opustí všetky zostávajúce vlákna démona. Démonové vlákna sa zvyčajne používajú na vykonávanie niektorých podporných alebo servisných úloh pre iné vlákna, mali by ste však vziať do úvahy, že môžu byť kedykoľvek opustené.

Ak chcete spustiť vlákno ako démon, mali by ste použiť setDaemon () metóda pred zavolaním štart ():

Thread daemon = new Thread (() -> System.out.println ("Hello from daemon!")); daemon.setDaemon (true); daemon.start ();

Je zaujímavé, že ak to prevádzkujete ako súčasť hlavný() metódou sa správa nemusí vytlačiť. To by sa mohlo stať, ak hlavný() vlákno by sa ukončilo skôr, ako by sa démon dostal k bodu vytlačenia správy. Spravidla by ste nemali robiť žiadne I / O v démonových vláknach, pretože ani nebudú môcť vykonávať svoje konečne blokuje a zatvorí zdroje, ak sa ich opustí.

Q6. Čo je príznak prerušenia vlákna? Ako to môžete nastaviť a skontrolovať? Ako to súvisí s prerušenou výnimkou?

Príznak prerušenia alebo stav prerušenia je interný Závit príznak, ktorý je nastavený pri prerušení vlákna. Ak ju chcete nastaviť, jednoducho zavolajte thread.interrupt () na objekte vlákna.

Ak je vlákno momentálne v jednej z metód, ktoré vyvolávajú Prerušená výnimka (počkaj, pripojiť sa, spať atď.), potom táto metóda okamžite vyvolá InterruptedException. Vlákno môže túto výnimku spracovať podľa svojej vlastnej logiky.

Ak vlákno nie je vo vnútri takejto metódy a thread.interrupt () sa volá, nedeje sa nič zvláštne. Je povinnosťou vlákna pravidelne kontrolovať stav prerušenia pomocou static Thread.interrupted () alebo inštancia je prerušený () metóda. Rozdiel medzi týmito metódami spočíva v tom, že: static Thread.interrupted () vymaže príznak prerušenia, zatiaľ čo je prerušený () nie.

Q7. Čo sú Exekútor a Exekútorská služba? Aké sú rozdiely medzi týmito rozhraniami?

Exekútor a ExecutorService sú dve súvisiace rozhrania java.util.concurrent rámec. Exekútor je veľmi jednoduché rozhranie s jedným vykonať metóda prijatia Spustiteľné inštancie na vykonanie. Vo väčšine prípadov je to rozhranie, na ktorom by mal závisieť váš kód vykonávajúci úlohu.

ExecutorService rozširuje Exekútor rozhranie s viacerými metódami na manipuláciu a kontrolu životného cyklu súbežnej služby vykonávania úloh (ukončenie úloh v prípade vypnutia) a metódami na zložitejšie asynchrónne spracovanie úloh vrátane Budúcnosť.

Ďalšie informácie o používaní Exekútor a ExecutorService, pozri článok Sprievodca Java ExecutorService.

Q8. Aké sú dostupné implementácie Executorservice v štandardnej knižnici?

The ExecutorService rozhranie má tri štandardné implementácie:

  • ThreadPoolExecutor - na vykonávanie úloh pomocou skupiny vlákien. Po dokončení vykonávania úlohy sa vlákno vráti späť do fondu. Ak sú všetky vlákna v skupine zaneprázdnené, musí si úloha počkať na svoju príležitosť.
  • ScheduledThreadPoolExecutor umožňuje naplánovať vykonávanie úloh namiesto okamžitého spustenia, keď je vlákno k dispozícii. Môže tiež plánovať úlohy s pevnou rýchlosťou alebo pevným oneskorením.
  • ForkJoinPool je zvláštny ExecutorService na zvládnutie úloh rekurzívnych algoritmov. Ak používate bežnú ThreadPoolExecutor pre rekurzívny algoritmus rýchlo zistíte, že všetky vaše vlákna sú zaneprázdnené čakaním na dokončenie nižších úrovní rekurzie. The ForkJoinPool implementuje takzvaný algoritmus krádeže práce, ktorý mu umožňuje efektívnejšie využívať dostupné vlákna.

Q9. Čo je Java Memory Model (Jmm)? Popíšte jeho účel a základné myšlienky.

Java Memory Model je súčasťou špecifikácie jazyka Java popísanej v kapitole 17.4. Určuje, ako viac vlákien pristupuje k spoločnej pamäti v súbežnej aplikácii Java a ako sú zmeny údajov v jednom vlákne viditeľné pre ostatné vlákna. Aj keď je JMM dosť krátky a výstižný, môže byť ťažké ho pochopiť bez silného matematického základu.

Potreba pamäťového modelu vyplýva zo skutočnosti, že spôsob, akým váš kód Java pristupuje k údajom, nie je taký, ako sa to v skutočnosti deje na nižších úrovniach. Usporiadanie a čítanie pamäte môže byť zmenené alebo optimalizované kompilátorom Java, kompilátorom JIT alebo dokonca CPU, pokiaľ je pozorovateľný výsledok týchto čítaní a zápisov rovnaký.

To môže viesť k protiintuitívnym výsledkom, keď je vaša aplikácia škálovaná na viac vlákien, pretože väčšina z týchto optimalizácií zohľadňuje jedno vlákno vykonávania (optimalizátory medzi vláknami sa implementujú stále mimoriadne ťažko). Ďalším obrovským problémom je, že pamäť v moderných systémoch je viacvrstvová: viacjadrové procesory môžu uchovávať niektoré nevyprázdnené údaje vo svojich vyrovnávacích pamätiach alebo vyrovnávacích pamätiach na čítanie a zápis, čo tiež ovplyvňuje stav pamäte pozorovaný z iných jadier.

Aby toho nebolo málo, existencia rôznych architektúr prístupu k pamäti by porušila prísľub Java „písať raz, bežať všade“. Našťastie pre programátorov JMM špecifikuje niektoré záruky, na ktoré sa môžete spoľahnúť pri navrhovaní viacvláknových aplikácií. Dodržiavanie týchto záruk pomáha programátorovi napísať viacvláknový kód, ktorý je stabilný a prenosný medzi rôznymi architektúrami.

Hlavné pojmy JMM sú:

  • Akcie, jedná sa o akcie medzi vláknami, ktoré môžu byť vykonávané jedným vláknom a detekované iným vláknom, ako napríklad čítanie alebo zápis premenných, uzamykanie / odomykanie monitorov a pod.
  • Synchronizačné akcie, určitá podmnožina akcií, napríklad čítanie / zápis a prchavý variabilná alebo zamykanie / odomykanie monitora
  • Programová objednávka (PO), pozorovateľné celkové poradie akcií v jednom vlákne
  • Synchronizačná objednávka (SO), celkové poradie medzi všetkými synchronizačnými akciami - musí to byť v súlade s programovým poradím, to znamená, že ak dve synchronizačné akcie prídu jedna po druhej v PO, dôjde k tomu v rovnakom poradí
  • synchronizuje-s (SW) vzťah medzi určitými synchronizačnými akciami, ako je odomknutie monitora a uzamknutie toho istého monitora (v inom alebo rovnakom vlákne)
  • Stane sa pred objednávkou - kombinuje PO so SW (toto sa nazýva prechodné uzavretie v teórii množín) na vytvorenie čiastočného usporiadania všetkých akcií medzi vláknami. Ak jedna akcia stane sa predtým pri inom, potom sú výsledky prvej akcie pozorovateľné pri druhej akcii (napríklad zápis premennej do jedného vlákna a čítanie v inom)
  • Stáva sa to pred konzistenciou - sada akcií je konzistentná s HB, ak každé čítanie sleduje buď posledný zápis na dané miesto v poradí pred uskutočnením, alebo nejaký iný zápis prostredníctvom dátového preteku
  • Exekúcia - určitý súbor nariadených akcií a pravidlá konzistentnosti medzi nimi

Pre daný program môžeme sledovať viacero rôznych vykonaní s rôznymi výsledkami. Ale ak program je správne synchronizované, potom sa zdajú byť všetky jeho popravy postupne konzistentné, čo znamená, že o viacvláknovom programe môžete uvažovať ako o súbore akcií vyskytujúcich sa v určitom poradí. Ušetrí vám to problém premýšľať o doobjednávaní, optimalizáciách alebo ukladaní údajov do medzipamäte.

Q10. Čo je to prchavé pole a aké záruky pre tento obor platí Jmm?

A prchavý pole má špeciálne vlastnosti podľa Java Memory Model (pozri Q9). Čítania a zápisy a prchavý premenné sú synchronizačné akcie, čo znamená, že majú celkové usporiadanie (všetky vlákna budú dodržiavať konzistentné poradie týchto akcií). Čítaním volatilnej premennej sa zaručene sleduje posledný zápis do tejto premennej podľa tohto poradia.

Ak máte pole, ku ktorému je prístup z viacerých vlákien a do ktorého je zapísané aspoň jedno vlákno, mali by ste zvážiť jeho vytvorenie prchavý, alebo inak existuje malá záruka toho, čo by určité vlákno čítalo z tohto poľa.

Ďalšia záruka pre prchavý je atomicita zápisu a čítania 64-bitových hodnôt (dlho a dvojitý). Bez volatilného modifikátora by čítanie takéhoto poľa mohlo pozorovať hodnotu čiastočne zapísanú iným vláknom.

Q11. Ktoré z nasledujúcich operácií sú atómové?

  • písanie naprchavýint;
  • písanie do a volatilná int;
  • písanie naprchavý dlhý;
  • písanie do a prchavý dlhý;
  • zvyšovanie a prchavý dlhý?

A napíšte na int (32-bitová) premenná je zaručene atómová, či už je prchavý alebo nie. A dlho (64-bitová) premenná mohla byť napísaná v dvoch samostatných krokoch, napríklad na 32-bitových architektúrach, takže štandardne neexistuje žiadna záruka atomicity. Ak však zadáte prchavý modifikátor, a dlho je zaručené, že k premennej sa bude pristupovať atómovo.

Operácia prírastku sa zvyčajne vykonáva vo viacerých krokoch (načítanie hodnoty, jej zmena a spätný zápis), takže nikdy nie je zaručené, že bude atómová, nech už je premenná prchavý alebo nie. Ak potrebujete implementovať atómový prírastok hodnoty, mali by ste použiť triedy AtomicInteger, AtomicLong atď.

Q12. Aké špeciálne záruky platí Jmm pre záverečné polia triedy?

JVM to v zásade zaručuje konečné polia triedy sa inicializujú skôr, ako sa akékoľvek vlákno zmocní objektu. Bez tejto záruky môže byť odkaz na objekt zverejnený, tj. Viditeľný, v inom vlákne pred inicializáciou všetkých polí tohto objektu z dôvodu zmeny poradia alebo iných optimalizácií. To by mohlo spôsobiť nenápadný prístup k týmto poliam.

To je dôvod, prečo by ste pri vytváraní nemenného objektu mali vždy robiť všetky jeho polia konečné, aj keď nie sú prístupné metódami getra.

Q13. Aký je význam synchronizovaného kľúčového slova v definícii metódy? statickej metódy? Pred blokom?

The synchronizované kľúčové slovo pred blokom znamená, že každé vlákno vstupujúce do tohto bloku musí získať monitor (objekt v zátvorkách). Ak je monitor už získaný iným vláknom, prvé vlákno vstúpi do ZABLOKOVANÉ stave a počkajte, kým sa monitor neuvoľní.

synchronizované (objekt) {// ...}

A synchronizované metóda inštancie má rovnakú sémantiku, ale samotná inštancia funguje ako monitor.

synchronized void instanceMethod () {// ...}

Pre statické synchronizované metódou je monitor Trieda objekt predstavujúci deklarujúcu triedu.

static synchronized void staticMethod () {// ...}

Q14. Ak dve vlákna volajú synchronizovanú metódu súčasne na rôznych inštanciách objektov, môže jedno z týchto vlákien blokovať? Čo ak je metóda statická?

Ak je metódou inštančná metóda, potom inštancia slúži ako monitor tejto metódy. Dve vlákna volajúce metódu v rôznych inštanciách získavajú rôzne monitory, takže žiadne z nich nebude blokované.

Ak je metóda statický, potom je monitorom Trieda objekt. Pre obe vlákna je monitor rovnaký, takže jeden z nich pravdepodobne zablokuje a počká, kým ďalší opustí rozhranie synchronizované metóda.

Q15. Aký je účel metód čakania, oznámenia a oznámenia triedy objektov?

Vlákno, ktoré vlastní monitor objektu (napríklad vlákno, ktoré vstúpilo do a synchronizované úsek strážený objektom) môže volať object.wait () dočasne uvoľniť monitor a dať šancu ďalším vláknam získať monitor. Môže sa tak stať napríklad na počkanie na určitý stav.

Keď podmienku splní ďalšie vlákno, ktoré získalo monitor, môže zavolať object.notify () alebo object.notifyAll () a uvoľnite monitor. The oznámiť metóda prebudí jedno vlákno v stave čakania a notifyAll metóda prebúdza všetky vlákna, ktoré čakajú na tento monitor, a všetky súťažia o opätovné získanie zámku.

Nasledujúci BlockingQueue Implementácia ukazuje, ako viaceré vlákna spolupracujú prostredníctvom počkaj-oznám vzor. Keby sme dať prvok do prázdneho frontu, všetky vlákna, ktoré čakali v priečinku vziať metóda prebudiť a pokúsiť sa získať hodnotu. Keby sme dať prvok do úplného radu, dať metóda počkajs za volanie do dostať metóda. The dostať metóda odstráni prvok a upozorní vlákna čakajúce v dať metóda, že vo fronte je prázdne miesto pre novú položku.

public class BlockingQueue {private List queue = new LinkedList (); limit súkromných int = 10; public synchronized void put (T item) {while (queue.size () == limit) {try {wait (); } chytit (InterruptedException e) {}} if (queue.isEmpty ()) {notifyAll (); } queue.add (item); } verejné synchronizované T take () hodí InterruptedException {while (queue.isEmpty ()) {try {wait (); } chytit (InterruptedException e) {}} if (queue.size () == limit) {notifyAll (); } návratová fronta.odstrániť (0); }}

Q16. Popíšte podmienky zablokovania, zablokovania a hladovania. Popíšte možné príčiny týchto stavov.

Uviaznutie je stav v skupine vlákien, ktorý nemôže dosiahnuť pokrok, pretože každé vlákno v skupine musí získať nejaký prostriedok, ktorý už získal iný vlákno v skupine. Najjednoduchším prípadom je, keď dve vlákna potrebujú pre postup uzamknutie oboch zdrojov, prvý zdroj je už uzamknutý jedným vláknom a druhý druhým. Tieto vlákna nikdy nezískajú zámok pre oba zdroje, a teda nikdy nebudú postupovať.

Livelock je prípad viacerých vlákien reagujúcich na podmienky alebo udalosti, ktoré generujú samy. Udalosť sa vyskytuje v jednom vlákne a musí byť spracovaná iným vláknom.Počas tohto spracovania dôjde k novej udalosti, ktorá sa musí spracovať v prvom vlákne atď. Takéto vlákna sú živé a nie sú blokované, ale napriek tomu nedosahujú žiadny pokrok, pretože sa navzájom zahlcujú zbytočnou prácou.

Hladovka je prípad vlákna, ktoré nedokáže získať zdroj, pretože iné vlákno (alebo vlákna) ho obsadzuje príliš dlho alebo má vyššiu prioritu. Vlákno nemôže napredovať, a teda nemôže vykonávať užitočnú prácu.

Q17. Popíšte účel a prípady použitia rámca vidlice / spojenia.

Rámec fork / join umožňuje paralelizáciu rekurzívnych algoritmov. Hlavný problém s paralelizáciou rekurzie pomocou niečoho ako ThreadPoolExecutor je, že by ste mohli rýchlo vyčerpať vlákna, pretože každý rekurzívny krok by vyžadoval svoje vlastné vlákno, zatiaľ čo vlákna v zásobníku by boli nečinné a čakali by.

Vstupným bodom rámca fork / join je ForkJoinPool triedy, ktorá je implementáciou ExecutorService. Implementuje algoritmus kradnutia práce, kde sa nečinné vlákna snažia „ukradnúť“ prácu rušným vláknam. To umožňuje rozložiť výpočty medzi rôzne vlákna a dosiahnuť pokrok pri použití menšieho počtu vlákien, ako by to vyžadovalo pri obvyklom fonde vlákien.

Viac informácií a ukážky kódu pre rámec fork / join nájdete v článku „Sprievodca po rozhraní Fork / join Framework v Jave“.

Ďalšie » Otázky týkajúce sa štruktúry triedy Java a inicializácie « Predchádzajúce otázky týkajúce sa rozhovoru so systémom typu Java

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