Java egyidejű interjúkérdések (+ válaszok)

Ez a cikk egy sorozat része: • Java Collections interjúkérdések

• Java típusú rendszerinterjúkérdések

• Java egyidejű interjúkérdések (+ válaszok) (aktuális cikk) • Java osztály felépítése és inicializálási interjúkérdések

• Java 8 interjúkérdések (+ válaszok)

• Memóriakezelés Java interjúkérdésekben (+ válaszok)

• Java Generics interjúkérdések (+ válaszok)

• Java Flow Control interjúkérdések (+ válaszok)

• Java kivételek interjúkérdések (+ válaszok)

• Java Annotations Interjúkérdések (+ Válaszok)

• A tavaszi keretrendszer legfontosabb interjúi

1. Bemutatkozás

A Java egyidejűsége az egyik legösszetettebb és legfejlettebb téma, amelyet a technikai interjúk során vetettek fel. Ez a cikk válaszokat ad az interjúval kapcsolatos néhány kérdésre, amelyekkel találkozhat.

Q1. Mi a különbség a folyamat és a szál között?

A folyamatok és a szálak is a párhuzamosság egységei, de alapvető különbségük van: a folyamatoknak nincs közös memóriájuk, míg a szálaknak igen.

Az operációs rendszer szempontjából a folyamat egy független szoftver, amely a saját virtuális memóriaterületén fut. Bármely többfeladatos operációs rendszernek (ami szinte minden modern operációs rendszert jelent) el kell választania a folyamatokat a memóriában, hogy egy meghibásodott folyamat ne húzza le az összes többi folyamatot a közös memória összekeverésével.

A folyamatok így általában elszigeteltek, és együttműködnek a folyamatok közötti kommunikáció révén, amelyet az operációs rendszer egyfajta köztes API-ként határoz meg.

Éppen ellenkezőleg, a szál egy olyan alkalmazás része, amely közös memóriában osztozik ugyanazon alkalmazás többi szálával. A közös memória használatával rengeteg rezsit lehet leborotválni, a szálakat sokkal gyorsabb együttműködésre és adatcserére tervezhetjük.

Q2. Hogyan hozhat létre szálpéldányt és futtathatja azt?

Egy szál példányának létrehozásához két lehetőség áll rendelkezésre. Először adja át a Futható példány a konstruktorához és a híváshoz Rajt(). Futható egy funkcionális interfész, így lambda kifejezésként továbbadható:

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

A szál is megvalósítja Futható, tehát egy szál indításának másik módja egy névtelen alosztály létrehozása, annak felülírása fuss() módszerrel, majd hívja meg Rajt():

Téma thread2 = új Téma () {@Orride public void run () {System.out.println ("Hello World from subclass!"); }}; thread2.start ();

Q3. Írja le a szál különböző állapotait és az állapotátmeneteket.

Az állapot a cérna segítségével ellenőrizhető Thread.getState () módszer. A különböző állapotai cérna a Menet. Állam enum. Ők:

  • ÚJ - egy új cérna például még nem indult el Thread.start ()
  • FUTHATÓ - futó szál. Futhatónak hívják, mert bármikor futhat, vagy várhatja a szálütemezőtől az idő következő kvantumát. A ÚJ szál belép a FUTHATÓ állapítsa meg, amikor hív Thread.start () Rajta
  • ZÁROLT - egy futó szál blokkolódik, ha be kell lépnie egy szinkronizált szakaszba, de ezt nem tudja megtenni egy másik szál miatt, amely a szakasz monitorját tartja
  • VÁRAKOZÁS - egy szál belép ebbe az állapotba, ha egy másik szálra vár egy adott művelet végrehajtására. Például egy szál ebbe az állapotba lép a Object.wait () módszer a monitorán, amelyet tart, vagy a Thread.join () módszer egy másik szálon
  • TIMED_WAITING - ugyanaz, mint a fentiek, de egy szál belép ebbe az állapotba, miután meghívta a Thread.sleep (), Object.wait (), Thread.join () és néhány más módszer
  • MEGSZŰNT - egy szál befejezte a végrehajtását Runnable.run () módszerrel és megszüntették

Q4. Mi a különbség a futható és a hívható interfészek között? Hogyan használják őket?

A Futható interfész egyetlen fuss módszer. Olyan számítási egységet képvisel, amelyet külön szálon kell futtatni. A Futható Az interfész nem engedi, hogy ez a módszer visszatérjen az értékhez, vagy hogy ellenőrizhetetlen kivételeket dobjon.

A Hívható interfész egyetlen hívás metódus, és olyan feladatot képvisel, amelynek van értéke. Ezért a hívás metódus értéket ad vissza. Kivételeket is dobhat. Hívható ban általában használják ExecutorService példányok aszinkron feladat indításához, majd a visszahívott hívásához Jövő például, hogy megkapja az értékét.

Q5. Mi a démonszál, mik a felhasználási esetei? Hogyan hozhat létre démonszálat?

A démon szál egy olyan szál, amely nem akadályozza meg a JVM kilépését. Amikor az összes nem démonos szál leáll, a JVM egyszerűen elhagyja az összes megmaradt démonszálat. A démonszálakat általában más szálakhoz kapcsolódó támogató vagy szolgáltató feladatok végrehajtására használják, de figyelembe kell vennie, hogy bármikor elhagyhatók.

A szál démonként történő indításához használja a setDaemon () metódust hívás előtt Rajt():

Száldémon = new Szál (() -> System.out.println ("Üdvözlet a démonból!")); daemon.setDaemon (true); daemon.start ();

Érdekes módon, ha ezt a fő() módszer esetén előfordulhat, hogy az üzenet nem lesz kinyomtatva. Ez akkor történhet meg, ha a fő() a szál véget ér, mielőtt a démon eljutna az üzenet kinyomtatásáig. Általában semmilyen I / O-t nem szabad végrehajtani démonszálakban, mivel azok nem is fogják tudni végrehajtani a fájljaikat végül blokkolja és zárja be az erőforrásokat, ha elhagyják.

Q6. Mi a szál megszakító zászlaja? Hogyan állíthatja be és ellenőrizheti? Hogyan viszonyul a megszakított kivételhez?

A megszakítási jelző vagy megszakítási állapot belső cérna zászló, amely akkor áll be, amikor a szál megszakad. Beállításához egyszerűen hívjon szál.megszakítás () a szál objektumon.

Ha egy szál jelenleg a dobó módszerek egyikében van InterruptedException (várjon, csatlakozik, alvás stb.), akkor ez a módszer azonnal megdobja az InterruptedException-t. A szál szabadon feldolgozhatja ezt a kivételt a saját logikája szerint.

Ha egy szál nincs az ilyen módszer belsejében, és szál.megszakítás () hívják, semmi különös nem történik. A szál felelőssége, hogy a megszakítás állapotát rendszeresen ellenőrizze a használatával statikus szál.megszakított () vagy példány isInterrupted () módszer. E módszerek között az a különbség, hogy a statikus szál.megszakított () törli a megszakítás jelzőt, miközben isInterrupted () nem.

Q7. Mi az a végrehajtó és a végrehajtó? Milyen különbségek vannak ezen interfészek között?

Végrehajtó és ExecutorService két kapcsolódó felülete java.util.egyidejű keretrendszer. Végrehajtó egy nagyon egyszerű felület egyetlen végrehajtani módszer elfogadása Futható végrehajtási példányok. A legtöbb esetben ez az a felület, amelyen a feladat végrehajtó kódjának függnie kell.

ExecutorService kiterjeszti a Végrehajtó interfész többféle módszerrel az egyidejű feladat-végrehajtási szolgáltatás életciklusának kezelésére és ellenőrzésére (a feladatok leállítása leállítás esetén), valamint a bonyolultabb aszinkron feladatkezelés módszereivel Határidős.

További információk a használatról Végrehajtó és ExecutorService, olvassa el A Java ExecutorService útmutatója című cikket.

Q8. Milyen elérhetők a végrehajtói szolgáltatások a standard könyvtárban?

A ExecutorService Az interfésznek három szabványos megvalósítása van:

  • ThreadPoolExecutor - feladatok végrehajtásához szálkészlet használatával. Miután egy szál befejezte a feladat végrehajtását, visszamegy a készletbe. Ha a készletben az összes szál foglalt, akkor a feladatnak meg kell várnia a sorát.
  • ScheduledThreadPoolExecutor lehetővé teszi a feladat végrehajtásának ütemezését ahelyett, hogy azonnal futtatná, amikor elérhető egy szál. A feladatokat ütemezett vagy fix késleltetéssel is ütemezheti.
  • ForkJoinPool egy különleges ExecutorService rekurzív algoritmus feladatok kezelésére. Ha szokásos ThreadPoolExecutor rekurzív algoritmus esetén gyorsan megtalálhatja, hogy az összes szála elfoglalt, és várja a rekurzió alacsonyabb szintjeinek befejezését. A ForkJoinPool megvalósítja az úgynevezett munka-lopási algoritmust, amely lehetővé teszi a rendelkezésre álló szálak hatékonyabb felhasználását.

Q9. Mi a Java memóriamodell (Jmm)? Írja le célját és alapgondolatait.

A Java memóriamodell a Java nyelvspecifikáció része, amelyet a 17.4. Fejezet ismertet. Megadja, hogy több szál hogyan fér hozzá a közös memóriához egyidejű Java alkalmazásban, és hogy az egyik szál által végrehajtott adatok hogyan láthatók a többi szál számára. Bár meglehetősen rövid és tömör, a JMM erős matematikai háttér nélkül nehéz megérteni.

A memóriamodell szükségessége abból adódik, hogy a Java kódja nem fér hozzá az adatokhoz, ahogyan ez valójában az alsó szinteken történik. A memória írásait és olvasásait a Java fordító, a JIT fordító és még a CPU is átrendezheti vagy optimalizálhatja, amennyiben ezeknek az olvasásoknak és írásoknak a megfigyelhető eredménye ugyanaz.

Ez ellentmondó eredményeket eredményezhet, ha az alkalmazás több szálra skálázódik, mivel ezeknek az optimalizálásoknak a többsége egyetlen végrehajtási szálat vesz figyelembe (a keresztmenetes optimalizálókat még mindig rendkívül nehéz megvalósítani). Egy másik óriási probléma, hogy a modern rendszerek memóriája többrétegű: a processzor több magja néhány nem átmosódott adatot tárolhat a gyorsítótárában vagy olvasási / írási puffereiben, ami szintén befolyásolja a többi magból megfigyelt memória állapotát.

A helyzetet tovább rontja, hogy a különböző memória-hozzáférési architektúrák megsértik a Java ígéretét, miszerint „írj egyszer, fuss mindenhova”. A programozók számára szerencsére a JMM meghatároz néhány garanciát, amelyekre támaszkodhat a többszálas alkalmazások tervezésénél. Ezekhez a garanciákhoz való ragaszkodás segít a programozónak többszálas kódot írni, amely stabil és hordozható a különféle architektúrák között.

A JMM főbb fogalmai a következők:

  • Műveletek, ezek egy szálon belüli műveletek, amelyeket egy szál végrehajthat és egy másik szál észlelhet, például változók olvasása vagy írása, monitorok lezárása / feloldása stb.
  • Szinkronizálási műveletek, a műveletek bizonyos részhalmaza, például olvasás / írás a illó változó, vagy a monitor lezárása / feloldása
  • Programrendelés (PO), a műveletek megfigyelhető teljes sorrendje egyetlen szálon belül
  • Szinkronizálási sorrend (SO), az összes szinkronizálási művelet közötti teljes sorrend - összhangban kell lennie a program sorrendjével, vagyis ha két szinkronizációs művelet egymás után jön a PO-ban, akkor ugyanabban a sorrendben fordulnak elő
  • szinkronizál -val (SW) kapcsolat bizonyos szinkronizálási műveletek között, például a monitor feloldása és ugyanazon monitor zárolása (egy másik vagy ugyanazon szálban)
  • A Rend előtt történik - egyesíti a PO-t az SW-vel (ezt hívják tranzitív lezárás halmazelméletben) a szálak közötti összes művelet részleges rendezéséhez. Ha egy cselekvés történik-korábban egy másik, akkor az első művelet eredményei megfigyelhetők a második művelettel (például írjon be egy változót egy szálba, és olvasson el egy másikba)
  • A következetesség előtt történik - a műveletek összessége HB-konzisztens, ha minden olvasás megfigyeli vagy az utolsó írást az adott helyre az események előtti sorrendben, vagy valamilyen más írást adatverseny útján
  • Végrehajtás - bizonyos rendezett cselekvések és következetességi szabályok közöttük

Egy adott program esetében több különböző végrehajtást figyelhetünk meg, különböző kimenetekkel. De ha egy program az helyesen szinkronizált, akkor úgy tűnik, hogy az összes kivégzése az egymás után következetes, ami azt jelenti, hogy a többszálú programot mint műveletsort valamilyen szekvenciális sorrendben indokolhatja. Ez megtakarítja Önt azzal, hogy gondot fordítson a motorháztető alatti átrendezésre, optimalizálásra vagy az adatok gyorsítótárazására.

Q10. Mi az illékony mező és milyen garanciákat vállal a Jmm egy ilyen mezőre?

A illó mező speciális tulajdonságokkal rendelkezik a Java memóriamodell szerint (lásd Q9). Olvas és ír a illó A változó szinkronizálási műveletek, vagyis teljes sorrendben vannak (az összes szál következetes sorrendet fog követni). Garantáltan egy illékony változó beolvasása figyeli a változó utolsó írását ebben a sorrendben.

Ha van olyan mezője, amelyhez több szál érhető el, és legalább egy szál ír rá, akkor fontolja meg annak létrehozását illó, különben van egy kis garancia arra, hogy mit olvas ki egy bizonyos szál erről a mezőről.

Egy másik garancia a illó a 64 bites értékek írásának és olvasásának atomossága (hosszú és kettős). Illékony módosító nélkül egy ilyen mező kiolvasása megfigyelhet egy másik szál által részben megírt értéket.

Q11. Az alábbi műveletek közül melyek az atomok?

  • írás egy nemillóint;
  • írás a illékony int;
  • írás egy nemillékony hosszú;
  • írás a illékony hosszú;
  • növekszik a illékony hosszú?

Írjon egy int A (32 bites) változó garantáltan atomi, függetlenül attól, hogy van-e illó vagy nem. A hosszú A (64 bites) változó két külön lépésben írható, például 32 bites architektúrákra, így alapértelmezés szerint nincs atomaritási garancia. Ha azonban megadja a illó módosító, a hosszú garantált, hogy a változó atomikusan elérhető.

A növekményes műveletet általában több lépésben hajtják végre (érték lekérése, megváltoztatása és visszaírás), így soha nem garantált, hogy atom, attól függetlenül, hogy a változó illó vagy nem. Ha egy érték atomi növekedését kell végrehajtania, akkor osztályokat kell használnia AtomicInteger, AtomicLong stb.

Q12. Milyen különleges garanciákat vállal a Jmm egy osztály utolsó mezőire?

A JVM alapvetően ezt garantálja végső Egy osztály mezői inicializálódnak, mielőtt bármelyik szál elkapná az objektumot. E garancia nélkül az objektumra vonatkozó összes hivatkozás közzétehető, vagyis láthatóvá válhat egy másik szálon, mielőtt az objektum összes mezőjét inicializálnák, az átrendezés vagy más optimalizálás miatt. Ez racionális hozzáférést okozhat ezekhez a mezőkhöz.

Ezért, amikor egy megváltoztathatatlan objektumot készít, mindig meg kell adnia az összes mezőjét végső, még akkor is, ha nem érhetők el jobb módszerekkel.

Q13. Mit jelent a szinkronizált kulcsszó a módszer meghatározásában? statikus módszer? Blokk előtt?

A szinkronizált A kulcsszó egy blokk előtt azt jelenti, hogy a blokkba belépő minden szálnak meg kell szereznie a monitort (zárójelben lévő objektumot). Ha a monitort már megszerezte egy másik szál, akkor az előbbi szál belép a ZÁROLT állapotot, és várja meg, amíg a monitort elengedi.

szinkronizált (objektum) {// ...}

A szinkronizált példány metódusnak ugyanaz a szemantikája, de maga a példány monitorként működik.

szinkronizált void példányMethod () {// ...}

A statikus szinkronban módszer, a monitor az Osztály a deklaráló osztályt képviselő objektum.

statikus szinkronizált void staticMethod () {// ...}

Q14. Ha két szál egyidejűleg hív szinkronizált módszert különböző objektumpéldányokon, akkor blokkolhatja e szálak egyikét? Mi van, ha a módszer statikus?

Ha a metódus egy példány metódus, akkor a példány a módszer figyelőjeként működik. Két szál, amely a metódust különböző esetekben hívja meg, különböző monitorokat szerez, így egyiket sem blokkolja.

Ha a módszer statikus, akkor a monitor a Osztály tárgy. Mindkét szál esetében a monitor megegyezik, így egyikük valószínűleg blokkolni fog, és megvárja, amíg egy másik kilép a szinkronizált módszer.

Q15. Mi a várakozási, értesítési és értesítési módszerek célja az objektumosztályban?

Szál, amely az objektum monitorjának tulajdonosa (például egy szál, amely belépett a szinkronizált objektum által őrzött szakasz) felhívhatja object.wait () hogy ideiglenesen elengedje a monitort és esélyt adjon más szálaknak a monitor megszerzésére. Ez történhet például egy bizonyos feltétel megvárására.

Amikor egy másik szál, amely megszerezte a monitort, teljesíti a feltételt, akkor hívhat object.notify () vagy object.notifyAll () és engedje el a monitort. A értesíteni metódus várakozási állapotban egyetlen szálat ébreszt, és a értesítMinden módszer felébreszti az összes szálat, amely erre a monitorra vár, és mindannyian versenyeznek a zár újbóli megszerzéséért.

A következő BlockingQueue A megvalósítás megmutatja, hogyan működik több szál együtt a várakozás-értesítés minta. Ha mi tedd egy elemet egy üres sorba, az összes szálat, amely a vesz módszer felébred, és megpróbálja megkapni az értéket. Ha mi tedd egy elem egy teljes sorba, az tedd módszer várjons a telefonhíváshoz kap módszer. A kap metódus eltávolít egy elemet, és értesíti a tedd módszer, hogy a sornak üres helye van egy új elem számára.

public class BlockingQueue {private List queue = new LinkedList (); private int limit = 10; public synchronized void put (T item) {while (queue.size () == limit) {try {wait (); } catch (InterruptedException e) {}} if (queue.isEmpty ()) {noticeAll (); } queue.add (elem); } nyilvános szinkronizált T take () dobja az InterruptedException {while (queue.isEmpty ()) {try {wait (); } catch (InterruptedException e) {}} if (queue.size () == korlát) {noticeAll (); } return queue.remove (0); }}

Q16. Írja le a holtpont, a livelock és az éhezés feltételeit. Írja le ezen állapotok lehetséges okait.

Holtpont egy olyan szálcsoporton belüli feltétel, amely nem tud haladni, mert a csoport minden szálának el kell szereznie valamilyen erőforrást, amelyet a csoport egy másik szála már megszerzett. A legegyszerűbb eset, amikor két szálnak le kell zárnia a két erőforrást a továbbjutáshoz, az első erőforrást már egy szál, a másodikat pedig egy másik szál zárja le. Ezek a szálak soha nem szereznek zárat mindkét erőforrás számára, és így soha nem fognak fejlődni.

Livelock egy olyan eset, amikor több szál reagál az önmaguk által generált feltételekre vagy eseményekre. Egy esemény egy szálban fordul elő, és egy másik szálnak kell feldolgoznia.A feldolgozás során új esemény következik be, amelyet az első szálban kell feldolgozni, és így tovább. Az ilyen szálak élnek és nincsenek elzárva, de mégsem haladnak előre, mert haszontalan munkával nyomasztják egymást.

Éhezés egy olyan szál esete, amely nem képes megszerezni az erőforrást, mert más szál (vagy szálak) túl sokáig foglalják el, vagy nagyobb prioritással rendelkeznek. Egy szál nem tud haladni, és így nem képes hasznos munkát végrehajtani.

Q17. Ismertesse a Fork / Join Framework célját és felhasználási eseteit.

A fork / join keretrendszer lehetővé teszi a rekurzív algoritmusok párhuzamosítását. A rekurzió párhuzamosításának fő problémája ilyesmi használatával ThreadPoolExecutor az, hogy gyorsan elfogyhatnak a szálak, mert minden rekurzív lépéshez saját szálra lenne szükség, míg a veremben lévő szálak tétlenek és várakoznának.

A fork / join keretrendszer belépési pontja a ForkJoinPool osztály, amely a ExecutorService. Megvalósítja a munka-lopási algoritmust, ahol a tétlen szálak megpróbálják „ellopni” a munkát elfoglalt szálakból. Ez lehetővé teszi a számítások elosztását különböző szálak között, és előrehaladást, miközben kevesebb szálat használ, mint amire egy szokásos szálkészletnél szükség lenne.

További információk és kódminták a fork / join keretrendszerről az “Útmutató a Java fork / join keretrendszerhez” című cikkben találhatók.

Következő » Java osztály felépítése és inicializálása Interjú kérdések « Korábbi Java típusú rendszerinterjúkérdések