CodeGym /Java blog /Véletlen /A legjobb 50 állásinterjú kérdés és válasz a Java Core-ho...
John Squirrels
Szint
San Francisco

A legjobb 50 állásinterjú kérdés és válasz a Java Core-hoz. 2. rész

Megjelent a csoportban
A legjobb 50 állásinterjú kérdés és válasz a Java Core-hoz. 1. részA legjobb 50 állásinterjú kérdés és válasz a Java Core-hoz.  2. rész – 1

Többszálú

24. Hogyan hozhatok létre új szálat a Java-ban?

Így vagy úgy, egy szál a Thread osztály használatával jön létre. De ennek többféle módja van…
  1. A java.lang.Thread öröklése .
  2. Valósítsa meg a java.lang.Runnable felületet – a Thread osztály konstruktora egy Runnable objektumot vesz fel.
Beszéljünk mindegyikről.

Örökölje a szál osztályt

Ebben az esetben az osztályunkat örököljük a java.lang.Thread fájlt . Van egy run() metódusa, és pont erre van szükségünk. Az új szál teljes élete és logikája ebben a módszerben lesz. Ez olyan, mint egy módszer az új szálhoz. Ezek után nincs más hátra, mint létrehozni az osztályunk egy objektumát, és meghívni a start() metódust. Ez létrehoz egy új szálat, és elkezdi végrehajtani a logikáját. Lássuk:

/**
* An example of how to create threads by inheriting the {@link Thread} class.
*/
class ThreadInheritance extends Thread {

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       ThreadInheritance threadInheritance1 = new ThreadInheritance();
       ThreadInheritance threadInheritance2 = new ThreadInheritance();
       ThreadInheritance threadInheritance3 = new ThreadInheritance();
       threadInheritance1.start();
       threadInheritance2.start();
       threadInheritance3.start();
   }
}
A konzol kimenete valami ilyesmi lesz:
szál-1 szál-0 szál-2
Vagyis még itt is azt látjuk, hogy a szálak nem sorrendben futnak le, hanem úgy, ahogy a JVM jónak látja a futtatását :)

Valósítsa meg a Runnable felületet

Ha ellenzi az öröklődést és/vagy már örököl valamilyen más osztályt, használhatja a java.lang.Runnable felületet. Itt az osztályunkat a run() metódus implementálásával valósítjuk meg , mint a fenti példában. Már csak a Thread objektumok létrehozása van hátra . Úgy tűnik, hogy több kódsor rosszabb. De tudjuk, hogy az öröklődés milyen kártékony, és jobb mindenképp elkerülni ;) Nézze meg:

/**
* An example of how to create threads from the {@link Runnable} interface.
* It's easier than easy — we implement this interface and then pass an instance of our object
* to the constructor.
*/
class ThreadInheritance implements Runnable {

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       ThreadInheritance runnable1 = new ThreadInheritance();
       ThreadInheritance runnable2 = new ThreadInheritance();
       ThreadInheritance runnable3 = new ThreadInheritance();

       Thread threadRunnable1 = new Thread(runnable1);
       Thread threadRunnable2 = new Thread(runnable2);
       Thread threadRunnable3 = new Thread(runnable3);

       threadRunnable1.start();
       threadRunnable2.start();
       threadRunnable3.start();
   }
}
És íme az eredmény:
szál-0 szál-1 szál-2

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

A legjobb 50 állásinterjú kérdés és válasz a Java Core-hoz.  2. rész – 2A folyamat és a szál a következő módokon különbözik:
  1. A futó programot folyamatnak nevezzük, de a szál a folyamat egy darabja.
  2. A folyamatok függetlenek, de a szálak egy folyamat részei.
  3. A folyamatok különböző címterekkel rendelkeznek a memóriában, de a szálak közös címteret használnak.
  4. A szálak közötti kontextusváltás gyorsabb, mint a folyamatok közötti váltás.
  5. A folyamatok közötti kommunikáció lassabb és drágább, mint a szálak közötti kommunikáció.
  6. A szülőfolyamatban végrehajtott változtatások nem érintik a leszármazott folyamatokat, de a szülő szálak változásai hatással lehetnek a gyermekszálra.

26. Milyen előnyei vannak a többszálú kezelésnek?

  1. A többszálú megoldás lehetővé teszi, hogy egy alkalmazás/program mindig reagáljon a bemenetre, még akkor is, ha már fut néhány háttérfeladat;
  2. A többszálú megoldás lehetővé teszi a feladatok gyorsabb végrehajtását, mivel a szálak egymástól függetlenül futnak;
  3. A többszálú megoldás a gyorsítótár-memória jobb kihasználását teszi lehetővé, mivel a szálak hozzáférhetnek a megosztott memória erőforrásaihoz;
  4. A többszálú megoldás csökkenti a szükséges szerverek számát, mivel egy szerver egyszerre több szálat is futtathat.

27. Milyen állapotok vannak egy szál életciklusában?

A legjobb 50 állásinterjú kérdés és válasz a Java Core-hoz.  2-3 rész
  1. Új: Ebben az állapotban a Thread objektum az új operátor használatával jön létre, de új szál még nem létezik. A szál addig nem indul el, amíg meg nem hívjuk a start() metódust.
  2. Futtatható: Ebben az állapotban a szál futásra kész a start() módszert hívják. A szálütemező azonban még nem választotta ki.
  3. Futás: Ebben az állapotban a szálütemező kiválaszt egy szálat a kész állapotból, és lefut.
  4. Várakozás/blokkolva: ebben az állapotban egy szál nem fut, de még mindig él, vagy egy másik szál befejezésére vár.
  5. Dead/Terminated: amikor egy szál kilép a run() metódusból, akkor halott vagy lezárt állapotban van.

28. Lehetőség van egy szál kétszeri futtatására?

Nem, nem indíthatunk újra egy szálat, mert miután egy szál elindul és lefut, Dead állapotba kerül. Ha mégis megpróbálunk kétszer elindítani egy szálat, akkor egy java.lang.IllegalThreadStateException jelenik meg. Lássuk:

class DoubleStartThreadExample extends Thread {

   /**
    * Simulate the work of a thread
    */
   public void run() {
	// Something happens. At this state, this is not essential.
   }

   /**
    * Start the thread twice
    */
   public static void main(String[] args) {
       DoubleStartThreadExample doubleStartThreadExample = new DoubleStartThreadExample();
       doubleStartThreadExample.start();
       doubleStartThreadExample.start();
   }
}
Kivétel lesz, amint a végrehajtás ugyanannak a szálnak a második elejére érkezik. Próbáld ki te is ;) Jobb ezt egyszer látni, mint százszor hallani.

29. Mi van, ha a run()-t közvetlenül a start() hívása nélkül hívja meg?

Igen, természetesen meghívhatja a run() metódust, de új szál nem jön létre, és a metódus nem fut külön szálon. Ebben az esetben van egy közönséges objektumunk, amely egy közönséges metódust hív. Ha a start() metódusról beszélünk , az már más kérdés. A metódus meghívásakor a JVM új szálat indít. Ez a szál viszont a mi módszerünket hívja ;) Nem hiszed el? Tessék, próbálja ki:

class ThreadCallRunExample extends Thread {

   public void run() {
       for (int i = 0; i < 5; i++) {
           System.out.print(i);
       }
   }

   public static void main(String args[]) {
       ThreadCallRunExample runExample1 = new ThreadCallRunExample();
       ThreadCallRunExample runExample2 = new ThreadCallRunExample();

       // Two ordinary methods will be called in the main thread, one after the other.
       runExample1.run();
       runExample2.run();
   }
}
És a konzol kimenete így fog kinézni:
0123401234
Mint látható, nem jött létre szál. Minden úgy működött, mint egy átlagos órán. Először az első objektum metódusát hajtották végre, majd a másodikat.

30. Mi az a démonszál?

A démonszál olyan szál, amely alacsonyabb prioritású feladatokat hajt végre, mint egy másik szál. Más szóval, a feladata olyan segédfeladatok elvégzése, amelyeket csak egy másik (fő) szállal együtt kell elvégezni. Számos démonszál van, amely automatikusan fut, mint például a szemétgyűjtés, a véglegesítő stb.

Miért fejezi be a Java egy démonszálat?

A démonszál egyetlen célja, hogy háttértámogatást nyújtson a felhasználói szál számára. Ennek megfelelően, ha a fő szál megszakad, akkor a JVM automatikusan leállítja az összes démonszálát.

A Thread osztály módszerei

A java.lang.Thread osztály két módszert biztosít a démonszálak kezeléséhez:
  1. public void setDaemon(boolean status) — Ez a metódus jelzi, hogy ez egy démonszál lesz-e. Az alapértelmezett érték false . Ez azt jelenti, hogy nem jönnek létre démonszálak, hacsak kifejezetten nem mondod.
  2. public boolean isDaemon() — Ez a metódus lényegében a démon változó gettere , amelyet az előző módszerrel állítottunk be.
Példa:

class DaemonThreadExample extends Thread {

   public void run() {
       // Checks whether this thread is a daemon
       if (Thread.currentThread().isDaemon()) {
           System.out.println("daemon thread");
       } else {
           System.out.println("user thread");
       }
   }

   public static void main(String[] args) {
       DaemonThreadExample thread1 = new DaemonThreadExample();
       DaemonThreadExample thread2 = new DaemonThreadExample();
       DaemonThreadExample thread3 = new DaemonThreadExample();

       // Make thread1 a daemon thread.
       thread1.setDaemon(true);

       System.out.println("daemon? " + thread1.isDaemon());
       System.out.println("daemon? " + thread2.isDaemon());
       System.out.println("daemon? " + thread3.isDaemon());

       thread1.start();
       thread2.start();
       thread3.start();
   }
}
Konzol kimenet:
démon? igaz démon? hamis démon? false démon szál felhasználói szál felhasználói szál
A kimenetből látjuk, hogy magán a szálon belül a static currentThread() metódussal kideríthetjük, melyik szálról van szó. Alternatív megoldásként, ha van hivatkozásunk a szál objektumra, akkor közvetlenül onnan is megtudhatjuk. Ez biztosítja a szükséges konfigurálhatósági szintet.

31. Lehetséges-e egy szálat démonná tenni a létrehozása után?

Nem. Ha megpróbálja ezt megtenni, IllegalThreadStateException kivételt kap . Ez azt jelenti, hogy csak egy démonszálat hozhatunk létre, mielőtt elindulna. Példa:

class SetDaemonAfterStartExample extends Thread {

   public void run() {
       System.out.println("Working...");
   }

   public static void main(String[] args) {
       SetDaemonAfterStartExample afterStartExample = new SetDaemonAfterStartExample();
       afterStartExample.start();
      
       // An exception will be thrown here
       afterStartExample.setDaemon(true);
   }
}
Konzol kimenet:
Működik... Kivétel a "main" szálban java.lang.IllegalThreadStateException itt: java.lang.Thread.setDaemon(Thread.java:1359) itt: SetDaemonAfterStartExample.main(SetDaemonAfterStartExample.java:14)

32. Mi az a leállító horog?

A shutdown hook egy olyan szál, amelyet a Java virtuális gép (JVM) leállítása előtt implicit módon hívnak meg. Így használhatjuk erőforrás felszabadítására vagy mentési állapotra, amikor a Java virtuális gép normálisan vagy rendellenesen leáll. Leállítási horgot a következő módszerrel adhatunk hozzá :

Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
Ahogy a példában látható:

/**
* A program that shows how to start a shutdown hook thread,
* which will be executed right before the JVM shuts down
*/
class ShutdownHookThreadExample extends Thread {

   public void run() {
       System.out.println("shutdown hook executed");
   }

   public static void main(String[] args) {

       Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());

       System.out.println("Now the program is going to fall asleep. Press Ctrl+C to terminate it.");
       try {
           Thread.sleep(60000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
}
Konzol kimenet:
Most a program elalszik. Nyomja meg a Ctrl+C billentyűkombinációt a befejezéshez. leállítási horog végrehajtva

33. Mi a szinkronizálás?

A Java-ban a szinkronizálás több szál hozzáférésének vezérlésének képessége bármely megosztott erőforráshoz. Ha több szál próbálja meg egyszerre végrehajtani ugyanazt a feladatot, hibás eredményt kaphat. A probléma megoldására a Java szinkronizálást használ, amely egyszerre csak egy szál futását teszi lehetővé. A szinkronizálást háromféleképpen lehet elérni:
  • Egy módszer szinkronizálása
  • Egy adott blokk szinkronizálása
  • Statikus szinkronizálás

Egy módszer szinkronizálása

Szinkronizált módszerrel zárolható egy objektum bármely megosztott erőforráshoz. Amikor egy szál szinkronizált metódust hív meg, akkor automatikusan felveszi az objektum zárolását, és feloldja azt, amikor a szál befejezi a feladatát. Ahhoz, hogy ez működjön, hozzá kell adnia a szinkronizált kulcsszót. Egy példán keresztül láthatjuk, hogyan működik ez:

/**
* An example where we synchronize a method. That is, we add the synchronized keyword to it.
* There are two authors who want to use one printer. Each of them has composed their own poems
* And of course they don’t want their poems mixed up. Instead, they want work to be performed in * * * order for each of them
*/
class Printer {

   synchronized void print(List<String> wordsToPrint) {
       wordsToPrint.forEach(System.out::print);
       System.out.println();
   }

   public static void main(String args[]) {
       // One object for two threads
       Printer printer  = new Printer();

       // Create two threads
       Writer1 writer1 = new Writer1(printer);
       Writer2 writer2 = new Writer2(printer);

       // Start them
       writer1.start();
       writer2.start();
   }
}

/**
* Author No. 1, who writes an original poem.
*/
class Writer1 extends Thread {
   Printer printer;

   Writer1(Printer printer) {
       this.printer = printer;
   }

   public void run() {
       List<string> poem = Arrays.asList("I ", this.getName(), " Write", " A Letter");
       printer.print(poem);
   }

}

/**
* Author No. 2, who writes an original poem.
*/
class Writer2 extends Thread {
   Printer printer;

   Writer2(Printer printer) {
       this.printer = printer;
   }

   public void run() {
       List<String> poem = Arrays.asList("I Do Not ", this.getName(), " Not Write", " No Letter");
       printer.print(poem);
   }
}
A konzol kimenete pedig ez:
I Thread-0 Írok egy levelet, nem szálak-1 Nem írok levelet

Szinkronizálási blokk

Egy szinkronizált blokk használható szinkronizálás végrehajtására egy metódus bármely adott erőforrásán. Tegyük fel, hogy egy nagy metódusban (igen, ezeket nem szabad leírni, de néha előfordulnak) valamiért csak egy kis részt kell szinkronizálni. Ha a metódus összes kódját egy szinkronizált blokkba helyezi, az ugyanúgy fog működni, mint a szinkronizált metódus. A szintaxis így néz ki:

synchronized ("object to be locked") {
   // The code that must be protected
}
Az előző példa megismétlődésének elkerülése érdekében anonim osztályok segítségével hozzuk létre a szálakat, azaz azonnal implementáljuk a Runnable felületet.

/**
* This is how a synchronization block is added.
* Inside the block, you need to specify which object's mutex will be acquired.
*/
class Printer {

   void print(List<String> wordsToPrint) {
       synchronized (this) {
           wordsToPrint.forEach(System.out::print);
       }
       System.out.println();
   }

   public static void main(String args[]) {
       // One object for two threads
       Printer printer = new Printer();

       // Create two threads
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("I ", "Writer1", " Write", " A Letter");
               printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("I Do Not ", "Writer2", " Not Write", " No Letter");
               printer.print(poem);
           }
       });

       // Start them
       writer1.start();
       writer2.start();
   }
}

}
A konzol kimenete pedig ez:
Írok1 Írok Levelet Nem Írok2 Nem Írok Nem Levelet

Statikus szinkronizálás

Ha egy statikus metódust szinkronizál, akkor a zárolás az osztályon történik, nem az objektumon. Ebben a példában statikus szinkronizálást hajtunk végre a szinkronizált kulcsszó statikus metódusra történő alkalmazásával:

/**
* This is how a synchronization block is added.
* Inside the block, you need to specify which object's mutex will be acquired.
*/
class Printer {

   static synchronized void print(List<String> wordsToPrint) {
       wordsToPrint.forEach(System.out::print);
       System.out.println();
   }

   public static void main(String args[]) {

       // Create two threads
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("I ", "Writer1", " Write", " A Letter");
               Printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("I Do Not ", "Writer2", " Not Write", " No Letter");
               Printer.print(poem);
           }
       });

       // Start them
       writer1.start();
       writer2.start();
   }
}
A konzol kimenete pedig ez:
Nem írok, nem írok, nem írok levelet

34. Mi az illékony változó?

A többszálú programozásban a volatile kulcsszót a szál biztonsága érdekében használják. Ha egy változtatható változót módosítunk, a változás minden más szál számára látható, így egy változót egyszerre csak egy szál használhat. A volatile kulcsszó használatával garantálhatja, hogy egy változó szálbiztos és megosztott memóriában tárolódik, és a szálak nem tárolják a gyorsítótárukban. Hogy néz ez ki?

private volatile AtomicInteger count;
Csak a volatilit adjuk a változóhoz. De ne feledd, hogy ez nem jelent teljes szálbiztonságot... Végül is a változón végzett műveletek nem biztos, hogy atomiak. Ennek ellenére használhat olyan Atomic osztályokat, amelyek atomszerűen, azaz egyetlen CPU-utasításban végzik a műveleteket. A java.util.concurrent.atomic csomagban sok ilyen osztály található .

35. Mi az a holtpont?

A Java-ban a holtpont olyan dolog, ami a többszálú feldolgozás részeként megtörténhet. Holtpont akkor fordulhat elő, ha egy szál egy objektum zárolására vár, amelyet egy másik szál szerez meg, a második szál pedig az objektum első szál általi zárására vár. Ez azt jelenti, hogy a két szál egymásra vár, és a kódjuk végrehajtása nem folytatódhat. A legjobb 50 állásinterjú kérdés és válasz a Java Core-hoz.  2-4 részTekintsünk egy példát, amelynek van egy osztálya, amely megvalósítja a Runnable-t. Konstruktora két erőforrást igényel. A run() metódus sorrendben szerzi be számukra a zárolást. Ha ebből az osztályból két objektumot hoz létre, és más sorrendben adja át az erőforrásokat, akkor könnyen patthelyzetbe kerülhet:

class DeadLock {

   public static void main(String[] args) {
       final Integer r1 = 10;
       final Integer r2 = 15;

       DeadlockThread threadR1R2 = new DeadlockThread(r1, r2);
       DeadlockThread threadR2R1 = new DeadlockThread(r2, r1);

       new Thread(threadR1R2).start();
       new Thread(threadR2R1).start();
   }
}

/**
* A class that accepts two resources.
*/
class DeadlockThread implements Runnable {

   private final Integer r1;
   private final Integer r2;

   public DeadlockThread(Integer r1, Integer r2) {
       this.r1 = r1;
       this.r2 = r2;
   }

   @Override
   public void run() {
       synchronized (r1) {
           System.out.println(Thread.currentThread().getName() + " acquired resource: " + r1);

           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }

           synchronized (r2) {
               System.out.println(Thread.currentThread().getName() + " acquired resource: " + r2);
           }
       }
   }
}
Konzol kimenet:
Az első szál megszerezte az első erőforrást A második szál megszerezte a második erőforrást

36. Hogyan kerülheti el a holtpontot?

Mivel tudjuk, hogyan történik a holtpont, levonhatunk néhány következtetést...
  • A fenti példában a holtpont annak a ténynek köszönhető, hogy beágyazott zárolást alkalmaztunk. Vagyis van egy szinkronizált blokkunk egy szinkronizált blokkon belül. Ennek elkerülése érdekében a beágyazás helyett új magasabb absztrakciós réteget kell létrehozni, a szinkronizálást magasabb szintre kell helyezni, és meg kell szüntetni a beágyazott zárolást.
  • Minél többet zár, annál valószínűbb, hogy holtpontra kerül sor. Ezért minden alkalommal, amikor szinkronizált blokkot ad hozzá, át kell gondolnia, hogy valóban szüksége van-e rá, és elkerülheti-e az új blokk hozzáadását.
  • A Thread.join() használata . Holtpontba is kerülhet, miközben az egyik szál a másikra vár. A probléma elkerülése érdekében érdemes lehet időtúllépést beállítani a join() metódushoz.
  • Ha van egy szálunk, akkor nem lesz holtpont ;)

37. Mi a versenyfeltétel?

Ha a valós versenyek autókat foglalnak magukban, akkor a többszálú versenyek szálakat tartalmaznak. De miért? :/ Két szál fut, és hozzáférhet ugyanahhoz az objektumhoz. És egyidejűleg megpróbálhatják frissíteni a megosztott objektum állapotát. Eddig minden világos, igaz? A szálak vagy szó szerint párhuzamosan (ha a processzornak egynél több magja van), vagy szekvenciálisan futnak le, miközben a processzor kiosztja az interleaved időszeleteket. Ezeket a folyamatokat nem tudjuk kezelni. Ez azt jelenti, hogy amikor egy szál adatokat olvas be egy objektumból, nem tudjuk garantálni, hogy lesz ideje megváltoztatni az objektumot, MIELŐTT egy másik szál megtenné. Ilyen problémák akkor merülnek fel, ha vannak ilyen „ellenőrizzünk és cselekedjünk” kombók. Az mit jelent? Tegyük fel, hogy van egy if utasítás, amelynek törzse magát az if-feltételt változtatja meg, például:

int z = 0;

// Check
if (z < 5) {
// Act
   z = z + 5;
}
Két szál egyszerre léphet be ebbe a kódblokkba, amikor z még mindig nulla, és akkor mindkét szál megváltoztathatja az értékét. Ennek eredményeként nem kapjuk meg a várt 5-ös értéket. Ehelyett 10-et kapunk. Hogyan lehet ezt elkerülni? Ellenőrzés és cselekvés előtt be kell szereznie egy zárat, majd ezt követően fel kell oldania. Vagyis az első szálnak be kell lépnie az if blokkba, végre kell hajtania az összes műveletet, módosítania kell z -t , és csak ezután kell megadnia a lehetőséget a következő szálnak, hogy ugyanezt tegye. De a következő szál nem lép be az if blokkba, mivel z most 5 lesz:

// Acquire the lock for z
if (z < 5) {
   z = z + 5;
}
// Release z's lock
===================================================

Konklúzió helyett

Szeretnék köszönetet mondani mindenkinek, aki a végéig elolvasta. Hosszú volt az út, de kibírtad! Talán nem minden világos. Ez normális. Amikor elkezdtem tanulni a Java-t, nem tudtam azon gondolkodni, hogy mi is az a statikus változó. De nem nagy baj. Aludtam rajta, elolvastam még pár forrást, aztán jött a megértés. Az interjúra való felkészülés inkább tudományos, mint gyakorlati kérdés. Ennek eredményeként minden interjú előtt át kell tekintenie és fel kell frissítenie emlékezetében azokat a dolgokat, amelyeket esetleg nem használ túl gyakran.

És mint mindig, itt is van néhány hasznos link:

Köszönöm mindenkinek, hogy elolvasta. Hamarosan találkozunk :) GitHub profilomA legjobb 50 állásinterjú kérdés és válasz a Java Core-hoz.  2. rész – 5
Hozzászólások
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION