CodeGym /Java blogg /Slumpmässig /Topp 50 frågor och svar på jobbintervjuer för Java Core. ...
John Squirrels
Nivå
San Francisco

Topp 50 frågor och svar på jobbintervjuer för Java Core. Del 2

Publicerad i gruppen
Topp 50 frågor och svar på jobbintervjuer för Java Core. Del 1Topp 50 frågor och svar på jobbintervjuer för Java Core.  Del 2 - 1

Multithreading

24. Hur skapar jag en ny tråd i Java?

På ett eller annat sätt skapas en tråd med hjälp av klassen Thread. Men det finns olika sätt att göra detta på...
  1. Ärv java.lang.Thread .
  2. Implementera java.lang.Runnable- gränssnittet — Thread -klassens konstruktor tar ett Runnable-objekt.
Låt oss prata om var och en av dem.

Ärva klassen Tråd

I det här fallet får vi vår klass att ärva java.lang.Thread . Den har en run()- metod, och det är precis vad vi behöver. Allt liv och logik i den nya tråden kommer att vara i denna metod. Det är ungefär som en huvudmetod för den nya tråden. Efter det återstår bara att skapa ett objekt av vår klass och anropa start()- metoden . Detta kommer att skapa en ny tråd och börja köra dess logik. Låt oss ta en titt:

/**
* 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();
   }
}
Konsolutgången blir ungefär så här:
Tråd-1 Tråd-0 Tråd-2
Det vill säga, även här ser vi att trådar körs inte i ordning, utan snarare som JVM finner lämpligt att köra dem :)

Implementera Runnable-gränssnittet

Om du är emot arv och/eller redan ärver någon annan klass, kan du använda gränssnittet java.lang.Runnable . Här får vi vår klass att implementera detta gränssnitt genom att implementera metoden run() , precis som i exemplet ovan. Allt som återstår är att skapa trådobjekt . Det verkar som att fler rader kod är värre. Men vi vet hur skadligt arv är och att det är bättre att undvika det med alla medel ;) Ta en titt:

/**
* 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();
   }
}
Och här är resultatet:
Tråd-0 Tråd-1 Tråd-2

25. Vad är skillnaden mellan en process och en tråd?

Topp 50 frågor och svar på jobbintervjuer för Java Core.  Del 2 - 2En process och en tråd är olika på följande sätt:
  1. Ett pågående program kallas en process, men en tråd är en del av en process.
  2. Processer är oberoende, men trådar är delar av en process.
  3. Processer har olika adressutrymmen i minnet, men trådar delar ett gemensamt adressutrymme.
  4. Kontextväxling mellan trådar är snabbare än att växla mellan processer.
  5. Kommunikation mellan processer är långsammare och dyrare än kommunikation mellan trådar.
  6. Eventuella ändringar i en överordnad process påverkar inte en underordnad process, men ändringar i en överordnad tråd kan påverka en underordnad tråd.

26. Vilka är fördelarna med multithreading?

  1. Multithreading tillåter en applikation/program att alltid vara lyhörd för input, även om det redan kör vissa bakgrundsuppgifter;
  2. Multithreading gör det möjligt att slutföra uppgifter snabbare, eftersom trådar körs oberoende;
  3. Multithreading ger bättre användning av cacheminne, eftersom trådar kan komma åt delade minnesresurser;
  4. Multithreading minskar antalet servrar som krävs, eftersom en server kan köra flera trådar samtidigt.

27. Vilka är tillstånden i en tråds livscykel?

Topp 50 frågor och svar på jobbintervjuer för Java Core.  Del 2 - 3
  1. Nytt: I det här tillståndet skapas Thread -objekt med den nya operatorn, men en ny tråd existerar inte ännu. Tråden startar inte förrän vi anropar start()- metoden.
  2. Körbar: I detta tillstånd är tråden redo att köras efter start() metod kallas. Den har dock ännu inte valts ut av trådschemaläggaren.
  3. Kör: I det här tillståndet väljer trådschemaläggaren en tråd från ett klart läge och den körs.
  4. Väntar/blockerad: i det här tillståndet körs inte en tråd, men den är fortfarande vid liv eller väntar på att en annan tråd ska slutföras.
  5. Död/avslutad: när en tråd lämnar run() -metoden är den i ett dött eller avslutat tillstånd.

28. Är det möjligt att köra en tråd två gånger?

Nej, vi kan inte starta om en tråd, för efter att en tråd startar och körs går den in i Död-tillståndet. Om vi ​​försöker starta en tråd två gånger, kommer en java.lang.IllegalThreadStateException att kastas. Låt oss ta en titt:

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();
   }
}
Det kommer att finnas ett undantag så snart körningen kommer till den andra starten av samma tråd. Prova själv ;) Det är bättre att se det här en gång än att höra om det hundra gånger.

29. Vad händer om du anropar run() direkt utan att anropa start()?

Ja, du kan säkert anropa metoden run() , men en ny tråd kommer inte att skapas, och metoden kommer inte att köras på en separat tråd. I det här fallet har vi ett vanligt objekt som anropar en vanlig metod. Om vi ​​pratar om metoden start() så är det en annan sak. När denna metod anropas startar JVM en ny tråd. Den här tråden kallar i sin tur vår metod ;) Tror du inte på det? Här, ge det ett försök:

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();
   }
}
Och konsolutgången kommer att se ut så här:
0123401234
Som ni ser skapades ingen tråd. Allt fungerade precis som i en vanlig klass. Först exekverades metoden för det första objektet och sedan det andra.

30. Vad är en demon-tråd?

En demontråd är en tråd som utför uppgifter med lägre prioritet än en annan tråd. Med andra ord, dess uppgift är att utföra hjälpuppgifter som endast behöver göras i samband med en annan (huvud) tråd. Det finns många demontrådar som körs automatiskt, till exempel sophämtning, finalizer, etc.

Varför avslutar Java en demon-tråd?

Demontrådens enda syfte är att ge bakgrundsstöd till en användares tråd. Följaktligen, om huvudtråden avslutas, avslutar JVM automatiskt alla sina demontrådar.

Metoder i trådklassen

Klassen java.lang.Thread tillhandahåller två metoder för att arbeta med en demon-tråd:
  1. public void setDaemon(boolesk status) — Denna metod indikerar om detta kommer att vara en demontråd. Standardinställningen är falsk . Det betyder att inga demontrådar kommer att skapas om du inte specifikt säger det.
  2. public boolean isDaemon() — Denna metod är i huvudsak en getter för demonvariabeln , som vi ställer in med den tidigare metoden.
Exempel:

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();
   }
}
Konsolutgång:
demon? sann demon? falsk demon? falsk demontråd användartråd användartråd
Från utgången ser vi att inuti själva tråden kan vi använda metoden static currentThread() för att ta reda på vilken tråd det är. Alternativt, om vi har en referens till trådobjektet, kan vi också ta reda på det direkt från det. Detta ger den nödvändiga nivån av konfigurerbarhet.

31. Är det möjligt att göra en tråd till en demon efter att den har skapats?

Nej. Om du försöker göra detta får du ett IllegalThreadStateException . Det betyder att vi bara kan skapa en demontråd innan den startar. Exempel:

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);
   }
}
Konsolutgång:
Fungerar... Undantag i tråden "main" java.lang.IllegalThreadStateException på java.lang.Thread.setDaemon(Thread.java:1359) på SetDaemonAfterStartExample.main(SetDaemonAfterStartExample.java:14)

32. Vad är en avstängningskrok?

En avstängningskrok är en tråd som implicit anropas innan Java Virtual Machine (JVM) stängs av. Således kan vi använda den för att frigöra en resurs eller spara tillstånd när den virtuella Java-maskinen stängs av normalt eller onormalt. Vi kan lägga till en avstängningskrok med följande metod:

Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
Som visas i exemplet:

/**
* 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();
       }
   }
}
Konsolutgång:
Nu ska programmet somna in. Tryck på Ctrl+C för att avsluta. avstängningskrok utförd

33. Vad är synkronisering?

I Java är synkronisering möjligheten att kontrollera åtkomst av flera trådar till valfri delad resurs. När flera trådar försöker utföra samma uppgift samtidigt kan du få ett felaktigt resultat. För att åtgärda det här problemet använder Java synkronisering, vilket gör att endast en tråd kan köras åt gången. Synkronisering kan uppnås på tre sätt:
  • Synkronisera en metod
  • Synkronisera ett specifikt block
  • Statisk synkronisering

Synkronisera en metod

En synkroniserad metod används för att låsa ett objekt för en delad resurs. När en tråd anropar en synkroniserad metod, förvärvar den automatiskt objektets lås och släpper det när tråden slutför sin uppgift. För att få detta att fungera måste du lägga till det synkroniserade nyckelordet. Vi kan se hur detta fungerar genom att titta på ett exempel:

/**
* 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);
   }
}
Och konsolutgången är denna:
Jag tråd-0 Skriv ett brev Jag gör inte tråd-1 Skriver inte något brev

Synkroniseringsblock

Ett synkroniserat block kan användas för att utföra synkronisering på någon speciell resurs i en metod. Låt oss säga att i en stor metod (ja, du bör inte skriva dem, men ibland händer de) du behöver bara synkronisera ett litet avsnitt av någon anledning. Om du lägger all metods kod i ett synkroniserat block kommer det att fungera på samma sätt som en synkroniserad metod. Syntaxen ser ut så här:

synchronized ("object to be locked") {
   // The code that must be protected
}
För att undvika att upprepa det föregående exemplet kommer vi att skapa trådar med hjälp av anonyma klasser, dvs vi kommer omedelbart att implementera Runnable-gränssnittet.

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

}
Och konsolutgången är denna:
Jag författare1 Skriv ett brev Jag gör inte författare2 Skriver inte något brev

Statisk synkronisering

Om du gör en statisk metod synkroniserad, kommer låsningen att ske på klassen, inte på objektet. I det här exemplet utför vi statisk synkronisering genom att tillämpa det synkroniserade nyckelordet på en statisk metod:

/**
* 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();
   }
}
Och konsolutgången är denna:
I Don Not Writer2 Not Write No Letter I Writer1 Skriv ett brev

34. Vad är en volatil variabel?

I flertrådsprogrammering används nyckelordet volatile för trådsäkerhet. När en föränderlig variabel modifieras är ändringen synlig för alla andra trådar, så en variabel kan användas av en tråd åt gången. Genom att använda nyckelordet volatile kan du garantera att en variabel är trådsäker och lagrad i delat minne, och att trådar inte lagrar den i sina cacheminne. Hur ser det här ut?

private volatile AtomicInteger count;
Vi lägger bara till volatile till variabeln. Men tänk på att detta inte betyder fullständig trådsäkerhet... Operationer på variabeln kanske inte är atomära. Som sagt, du kan använda Atomic- klasser som gör operationer atomärt, dvs i en enda CPU-instruktion. Det finns många sådana klasser i java.util.concurrent.atomic -paketet.

35. Vad är dödläge?

I Java är dödläge något som kan hända som en del av multithreading. Ett dödläge kan uppstå när en tråd väntar på ett objekts lås som förvärvats av en annan tråd, och den andra tråden väntar på objektets lås som förvärvats av den första tråden. Detta betyder att de två trådarna väntar på varandra, och exekveringen av deras kod kan inte fortsätta. Topp 50 frågor och svar på jobbintervjuer för Java Core.  Del 2 - 4Låt oss överväga ett exempel som har en klass som implementerar Runnable. Dess konstruktör tar två resurser. Metoden run() hämtar låset för dem i ordning. Om du skapar två objekt av den här klassen och skickar resurserna i en annan ordning, kan du enkelt hamna i dödläge:

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);
           }
       }
   }
}
Konsolutgång:
Den första tråden fick den första resursen Den andra tråden fick den andra resursen

36. Hur undviker du dödläge?

Eftersom vi vet hur dödläge uppstår kan vi dra några slutsatser...
  • I exemplet ovan uppstår dödläget på grund av att vi har kapslad låsning. Det vill säga, vi har ett synkroniserat block inuti ett synkroniserat block. För att undvika detta, istället för att kapsla, måste du skapa ett nytt högre abstraktionslager, flytta synkroniseringen till den högre nivån och eliminera den kapslade låsningen.
  • Ju mer låsning du gör, desto mer sannolikt kommer det att bli ett dödläge. Därför måste du varje gång du lägger till ett synkroniserat block fundera på om du verkligen behöver det och om du kan undvika att lägga till ett nytt.
  • Använder Thread.join() . Du kan också hamna i dödläge medan en tråd väntar på en annan. För att undvika detta problem kan du överväga att ställa in en timeout för metoden join() .
  • Om vi ​​har en tråd så blir det inget dödläge ;)

37. Vad är ett rasvillkor?

Om verkliga lopp involverar bilar, så involverar lopp i multithreading trådar. Men varför? :/ Det finns två trådar som körs och kan komma åt samma objekt. Och de kan försöka uppdatera det delade objektets tillstånd samtidigt. Allt är klart än så länge, eller hur? Trådar exekveras antingen bokstavligen parallellt (om processorn har mer än en kärna) eller sekventiellt, med processorn som allokerar interfolierade tidssegment. Vi kan inte hantera dessa processer. Det betyder att när en tråd läser data från ett objekt kan vi inte garantera att den hinner ändra objektet INNAN någon annan tråd gör det. Sådana problem uppstår när vi har dessa "check-and-act"-kombos. Vad betyder det? Anta att vi har en if- sats vars kropp ändrar själva if-villkoret, till exempel:

int z = 0;

// Check
if (z < 5) {
// Act
   z = z + 5;
}
Två trådar kan samtidigt komma in i detta kodblock när z fortfarande är noll och då kan båda trådarna ändra sitt värde. Som ett resultat kommer vi inte att få det förväntade värdet på 5. Istället skulle vi få 10. Hur undviker du detta? Du måste skaffa ett lås innan du kontrollerar och agerar, och släpp sedan låset efteråt. Det vill säga, du måste låta den första tråden gå in i if- blocket, utföra alla åtgärder, ändra z och först då ge nästa tråd möjlighet att göra detsamma. Men nästa tråd kommer inte in i if- blocket, eftersom z nu blir 5:

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

Istället för en slutsats

Jag vill säga tack till alla som läst till slutet. Det var långt, men du orkade! Kanske inte allt är klart. Det här är normalt. När jag först började studera Java, kunde jag inte linda hjärnan kring vad en statisk variabel är. Men ingen stor grej. Jag sov på den, läste några källor till och sedan kom förståelsen. Att förbereda sig för en intervju är mer en akademisk fråga snarare än en praktisk fråga. Som ett resultat bör du före varje intervju granska och fräscha upp i ditt minne de saker som du kanske inte använder så ofta.

Och som alltid, här är några användbara länkar:

Tack alla för att ni läser. Vi ses snart :) Min GitHub-profilTopp 50 frågor och svar på jobbintervjuer för Java Core.  Del 2 - 5
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION