CodeGym /Java Blog /Willekeurig /Top 50 sollicitatievragen en antwoorden voor Java Core. D...
John Squirrels
Niveau 41
San Francisco

Top 50 sollicitatievragen en antwoorden voor Java Core. Deel 2

Gepubliceerd in de groep Willekeurig
Top 50 sollicitatievragen en antwoorden voor Java Core. Deel 1Top 50 sollicitatievragen en antwoorden voor Java Core.  Deel 2 - 1

Multithreading

24. Hoe maak ik een nieuwe thread aan in Java?

Op de een of andere manier wordt een thread gemaakt met behulp van de klasse Thread. Maar er zijn verschillende manieren om dit te doen...
  1. Neem java.lang.Thread over .
  2. Implementeer de interface java.lang.Runnable — de constructor van de klasse Thread gebruikt een object Runnable.
Laten we het over elk van hen hebben.

Erf de klasse Thread

In dit geval laten we onze klasse java.lang.Thread erven . Het heeft een methode run() en dat is precies wat we nodig hebben. Al het leven en de logica van de nieuwe thread zal in deze methode zitten. Het is een soort hoofdmethode voor de nieuwe thread. Daarna hoeft u alleen nog maar een object van onze klasse te maken en de methode start() aan te roepen . Hiermee wordt een nieuwe thread gemaakt en wordt de logica ervan uitgevoerd. Laten we kijken:

/**
* 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();
   }
}
De console-uitvoer zal ongeveer zo zijn:
Draad-1 Draad-0 Draad-2
Dat wil zeggen, zelfs hier zien we dat threads niet in volgorde worden uitgevoerd, maar zoals de JVM het geschikt acht om ze uit te voeren :)

Implementeer de Runnable-interface

Als je tegen overerving bent en/of al een andere klasse hebt geërfd, kun je de interface java.lang.Runnable gebruiken. Hier laten we onze klasse deze interface implementeren door de methode run() te implementeren , net als in het bovenstaande voorbeeld. Het enige dat overblijft is het maken van Thread- objecten. Het lijkt erop dat meer regels code erger zijn. Maar we weten hoe schadelijk overerving is en dat het beter is om het op alle mogelijke manieren te vermijden ;) Kijk maar:

/**
* 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();
   }
}
En hier is het resultaat:
Draad-0 Draad-1 Draad-2

25. Wat is het verschil tussen een proces en een thread?

Top 50 sollicitatievragen en antwoorden voor Java Core.  Deel 2 - 2Een proces en een thread verschillen op de volgende manieren:
  1. Een lopend programma wordt een proces genoemd, maar een thread is een onderdeel van een proces.
  2. Processen zijn onafhankelijk, maar threads zijn onderdelen van een proces.
  3. Processen hebben verschillende adresruimten in het geheugen, maar threads delen een gemeenschappelijke adresruimte.
  4. Contextwisseling tussen threads gaat sneller dan schakelen tussen processen.
  5. Communicatie tussen processen is langzamer en duurder dan communicatie tussen threads.
  6. Wijzigingen in een bovenliggend proces hebben geen invloed op een onderliggend proces, maar wijzigingen in een bovenliggende thread kunnen wel van invloed zijn op een onderliggende thread.

26. Wat zijn de voordelen van multithreading?

  1. Multithreading stelt een applicatie/programma in staat om altijd te reageren op input, zelfs als het al wat achtergrondtaken uitvoert;
  2. Multithreading maakt het mogelijk om taken sneller af te ronden, omdat threads onafhankelijk van elkaar draaien;
  3. Multithreading zorgt voor een beter gebruik van cachegeheugen, omdat threads toegang hebben tot gedeelde geheugenbronnen;
  4. Multithreading vermindert het aantal benodigde servers, omdat één server meerdere threads tegelijk kan uitvoeren.

27. Wat zijn de toestanden in de levenscyclus van een thread?

Top 50 sollicitatievragen en antwoorden voor Java Core.  Deel 2 - 3
  1. Nieuw: in deze status wordt het Thread- object gemaakt met de nieuwe operator, maar er bestaat nog geen nieuwe thread. De thread start pas als we de methode start() aanroepen .
  2. Uitvoerbaar: in deze status is de thread klaar om te worden uitgevoerd na de start() methode wordt genoemd. Het is echter nog niet geselecteerd door de threadplanner.
  3. Actief: in deze status kiest de threadplanner een thread uit een gereedstatus en wordt deze uitgevoerd.
  4. Wachtend/geblokkeerd: in deze status is een thread niet actief, maar leeft deze nog of wacht tot een andere thread is voltooid.
  5. Dead/Terminated: wanneer een thread de methode run() verlaat , bevindt deze zich in een dode of beëindigde status.

28. Is het mogelijk om een ​​thread twee keer te laten lopen?

Nee, we kunnen een thread niet opnieuw starten, want nadat een thread is gestart en uitgevoerd, gaat deze naar de status Dood. Als we twee keer proberen een thread te starten, wordt een java.lang.IllegalThreadStateException gegenereerd. Laten we kijken:

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();
   }
}
Er zal een uitzondering zijn zodra de uitvoering komt bij de tweede start van dezelfde thread. Probeer het zelf ;) Het is beter om dit een keer te zien dan om er honderd keer over te horen.

29. Wat als u run() rechtstreeks aanroept zonder start() aan te roepen?

Ja, je kunt zeker de methode run() aanroepen , maar er wordt geen nieuwe thread gemaakt en de methode wordt niet op een aparte thread uitgevoerd. In dit geval hebben we een gewoon object dat een gewone methode aanroept. Als we het hebben over de start() methode, dan is dat een andere zaak. Wanneer deze methode wordt aangeroepen, start de JVM een nieuwe thread. Deze draad roept op zijn beurt onze methode aan ;) Geloof het niet? Hier, probeer het eens:

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();
   }
}
En de console-uitvoer ziet er als volgt uit:
0123401234
Zoals je kunt zien, is er geen thread gemaakt. Alles werkte net als in een gewone klas. Eerst werd de methode van het eerste object uitgevoerd en daarna de tweede.

30. Wat is een daemon-thread?

Een daemon-thread is een thread die taken uitvoert met een lagere prioriteit dan een andere thread. Met andere woorden, het is zijn taak om hulptaken uit te voeren die alleen in combinatie met een andere (hoofd)draad hoeven te worden uitgevoerd. Er zijn veel daemon-threads die automatisch worden uitgevoerd, zoals garbage collection, finalizer, enz.

Waarom beëindigt Java een daemon-thread?

Het enige doel van de daemon-thread is om achtergrondondersteuning te bieden aan de thread van een gebruiker. Dienovereenkomstig, als de hoofdthread wordt beëindigd, beëindigt de JVM automatisch al zijn daemon-threads.

Methoden van de klasse Thread

De klasse java.lang.Thread biedt twee methoden voor het werken met een daemon-thread:
  1. public void setDaemon(boolean status) — Deze methode geeft aan of dit een daemon-thread zal zijn. De standaardwaarde is onwaar . Dit betekent dat er geen daemon-threads worden gemaakt, tenzij u dit specifiek zegt.
  2. public boolean isDaemon() — Deze methode is in wezen een getter voor de daemon- variabele, die we hebben ingesteld met de vorige methode.
Voorbeeld:

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();
   }
}
Console-uitvoer:
demon? echte demon? valse demon? false daemon-thread gebruikersthread gebruikersthread
Uit de uitvoer zien we dat we binnen de thread zelf de statische currentThread() methode kunnen gebruiken om erachter te komen welke thread het is. Als we een verwijzing naar het thread-object hebben, kunnen we er ook rechtstreeks uit komen. Dit zorgt voor het nodige niveau van configureerbaarheid.

31. Is het mogelijk om van een thread een daemon te maken nadat deze is gemaakt?

Nee. Als u dit probeert, krijgt u een IllegalThreadStateException . Dit betekent dat we alleen een daemon-thread kunnen maken voordat deze start. Voorbeeld:

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);
   }
}
Console-uitvoer:
Werken... Uitzondering in thread "main" java.lang.IllegalThreadStateException op java.lang.Thread.setDaemon(Thread.java:1359) op SetDaemonAfterStartExample.main(SetDaemonAfterStartExample.java:14)

32. Wat is een shutdown-hook?

Een shutdown-hook is een thread die impliciet wordt aangeroepen voordat de Java virtual machine (JVM) wordt afgesloten. We kunnen het dus gebruiken om een ​​bron vrij te geven of een status op te slaan wanneer de virtuele Java-machine normaal of abnormaal wordt afgesloten. We kunnen een shutdown-hook toevoegen met behulp van de volgende methode:

Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
Zoals getoond in het voorbeeld:

/**
* 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();
       }
   }
}
Console-uitvoer:
Nu gaat het programma in slaap vallen. Druk op Ctrl+C om het te beëindigen. shutdown-hook uitgevoerd

33. Wat is synchronisatie?

In Java is synchronisatie de mogelijkheid om de toegang van meerdere threads tot een gedeelde bron te regelen. Wanneer meerdere threads tegelijkertijd dezelfde taak proberen uit te voeren, kunt u een onjuist resultaat krijgen. Om dit probleem op te lossen, gebruikt Java synchronisatie, waardoor slechts één thread tegelijk kan worden uitgevoerd. Synchronisatie kan op drie manieren worden bereikt:
  • Een methode synchroniseren
  • Een specifiek blok synchroniseren
  • Statische synchronisatie

Een methode synchroniseren

Er wordt een gesynchroniseerde methode gebruikt om een ​​object te vergrendelen voor een gedeelde bron. Wanneer een thread een gesynchroniseerde methode aanroept, verwerft deze automatisch de vergrendeling van het object en geeft deze vrij wanneer de thread zijn taak voltooit. Om dit te laten werken, moet u het gesynchroniseerde trefwoord toevoegen . We kunnen zien hoe dit werkt door naar een voorbeeld te kijken:

/**
* 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);
   }
}
En de console-uitvoer is dit:
Ik rijg-0 Schrijf een brief Ik rijg niet-1 Schrijf geen brief

Synchronisatieblok

Een gesynchroniseerd blok kan worden gebruikt om synchronisatie uit te voeren op een bepaalde bron in een methode. Laten we zeggen dat je in een grote methode (ja, je zou ze niet moeten schrijven, maar soms gebeuren ze) om de een of andere reden slechts een klein gedeelte hoeft te synchroniseren. Als u alle code van de methode in een gesynchroniseerd blok plaatst, werkt het hetzelfde als een gesynchroniseerde methode. De syntaxis ziet er als volgt uit:

synchronized ("object to be locked") {
   // The code that must be protected
}
Om herhaling van het vorige voorbeeld te voorkomen, zullen we threads maken met behulp van anonieme klassen, dwz we zullen onmiddellijk de Runnable-interface implementeren.

/**
* 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();
   }
}

}
En de console-uitvoer is dit:
Ik Schrijver1 Schrijf een brief Ik niet Schrijver2 Schrijf geen brief

Statische synchronisatie

Als u een statische methode synchroniseert, vindt de vergrendeling plaats op de klasse, niet op het object. In dit voorbeeld voeren we statische synchronisatie uit door het sleutelwoord synchroon toe te passen op een statische methode:

/**
* 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();
   }
}
En de console-uitvoer is dit:
Ik niet Schrijver2 Schrijf geen brief Ik Schrijver1 Schrijf een brief

34. Wat is een vluchtige variabele?

Bij multithreaded programmeren wordt het vluchtige trefwoord gebruikt voor threadveiligheid. Wanneer een veranderlijke variabele wordt gewijzigd, is de wijziging zichtbaar voor alle andere threads, zodat een variabele door één thread tegelijk kan worden gebruikt. Door het vluchtige trefwoord te gebruiken, kunt u garanderen dat een variabele thread-safe is en wordt opgeslagen in gedeeld geheugen, en dat threads deze niet in hun cache opslaan. Hoe ziet dit eruit?

private volatile AtomicInteger count;
We voegen gewoon vluchtig toe aan de variabele. Maar houd er rekening mee dat dit geen volledige thread-veiligheid betekent... De bewerkingen op de variabele zijn immers mogelijk niet atomair. Dat gezegd hebbende, kunt u Atomic- klassen gebruiken die bewerkingen atomisch uitvoeren, dat wil zeggen in een enkele CPU-instructie. Er zijn veel van dergelijke klassen in het pakket java.util.concurrent.atomic .

35. Wat is een impasse?

In Java is deadlock iets dat kan gebeuren als onderdeel van multithreading. Er kan een impasse optreden wanneer een thread wacht op de vergrendeling van een object die is verkregen door een andere thread, en de tweede thread wacht op de vergrendeling van het object die is verkregen door de eerste thread. Dit betekent dat de twee threads op elkaar wachten en dat de uitvoering van hun code niet kan doorgaan. Top 50 sollicitatievragen en antwoorden voor Java Core.  Deel 2 - 4Laten we eens kijken naar een voorbeeld met een klasse die Runnable implementeert. De constructor neemt twee bronnen in beslag. De methode run() verwerft de vergrendeling voor hen op volgorde. Als u twee objecten van deze klasse maakt en de bronnen in een andere volgorde doorgeeft, kunt u gemakkelijk in een impasse terechtkomen:

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);
           }
       }
   }
}
Console-uitvoer:
De eerste thread heeft de eerste resource verkregen De tweede thread heeft de tweede resource verkregen

36. Hoe voorkom je een impasse?

Omdat we weten hoe een impasse ontstaat, kunnen we enkele conclusies trekken...
  • In het bovenstaande voorbeeld treedt de deadlock op vanwege het feit dat we geneste vergrendeling hebben. Dat wil zeggen, we hebben een gesynchroniseerd blok binnen een gesynchroniseerd blok. Om dit te voorkomen, moet u in plaats van nesten een nieuwe, hogere abstractielaag maken, de synchronisatie naar een hoger niveau verplaatsen en de geneste vergrendeling elimineren.
  • Hoe vaker je vergrendelt, hoe groter de kans dat er een impasse ontstaat. Daarom moet u elke keer dat u een gesynchroniseerd blok toevoegt, nadenken of u het echt nodig heeft en of u kunt voorkomen dat u een nieuw blok toevoegt.
  • Thread.join() gebruiken . Je kunt ook vastlopen terwijl de ene thread op de andere wacht. Om dit probleem te voorkomen, kunt u overwegen een time-out in te stellen voor de methode join() .
  • Als we één draad hebben, is er geen impasse ;)

37. Wat is een raceconditie?

Als bij echte races auto's betrokken zijn, dan hebben races in multithreading betrekking op threads. Maar waarom? :/ Er zijn twee threads die actief zijn en toegang hebben tot hetzelfde object. En ze kunnen tegelijkertijd proberen de status van het gedeelde object bij te werken. Alles is tot nu toe duidelijk, toch? Threads worden letterlijk parallel uitgevoerd (als de processor meer dan één kern heeft) of opeenvolgend, waarbij de processor interleave tijdsegmenten toewijst. Wij kunnen deze processen niet managen. Dit betekent dat wanneer een thread gegevens van een object leest, we niet kunnen garanderen dat het tijd heeft om het object te wijzigen VOORDAT een andere thread dit doet. Dergelijke problemen doen zich voor wanneer we deze "check-and-act"-combo's hebben. Wat betekent dat? Stel dat we een if- statement hebben waarvan het lichaam de if-conditie zelf verandert, bijvoorbeeld:

int z = 0;

// Check
if (z < 5) {
// Act
   z = z + 5;
}
Twee threads kunnen tegelijkertijd dit codeblok invoeren als z nog steeds nul is en dan kunnen beide threads de waarde ervan wijzigen. Als gevolg hiervan krijgen we niet de verwachte waarde van 5. In plaats daarvan krijgen we 10. Hoe voorkom je dit? U moet een slot verkrijgen voordat u controleert en handelt, en daarna het slot ontgrendelen. Dat wil zeggen, je moet de eerste thread het if- blok laten invoeren, alle acties uitvoeren, z wijzigen en pas daarna de volgende thread de kans geven hetzelfde te doen. Maar de volgende thread komt niet in het if- blok, aangezien z nu 5 is:

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

In plaats van een conclusie

Ik wil iedereen bedanken die tot het einde heeft gelezen. Het was een lange weg, maar je hebt het volgehouden! Misschien is niet alles duidelijk. Dit is normaal. Toen ik Java voor het eerst begon te bestuderen, kon ik niet begrijpen wat een statische variabele is. Maar geen probleem. Ik sliep erop, las nog een paar bronnen en toen kwam het begrip. Het voorbereiden van een interview is meer een academische vraag dan een praktische. Daarom moet u vóór elk interview die dingen die u misschien niet vaak gebruikt, herhalen en in uw geheugen opfrissen.

En zoals altijd zijn hier enkele nuttige links:

Bedankt allemaal voor het lezen. Tot ziens :) Mijn GitHub-profielTop 50 sollicitatievragen en antwoorden voor Java Core.  Deel 2 - 5
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION