Top 50 jobsamtale spørgsmål og svar til Java Core. Del 1Top 50 jobsamtale spørgsmål og svar til Java Core.  Del 2 - 1

Multithreading

24. Hvordan opretter jeg en ny tråd i Java?

På en eller anden måde oprettes en tråd ved hjælp af klassen Thread. Men der er forskellige måder at gøre dette på...
  1. Arv java.lang.Thread .
  2. Implementer java.lang.Runnable- grænsefladen — Thread- klassens konstruktør tager et Runnable-objekt.
Lad os tale om hver af dem.

Arv klassen Tråd

I dette tilfælde får vi vores klasse til at arve java.lang.Thread . Den har en run()- metode, og det er lige, hvad vi har brug for. Alt liv og logik i den nye tråd vil være i denne metode. Det er lidt ligesom en hovedmetode til den nye tråd. Derefter er der kun tilbage at oprette et objekt af vores klasse og kalde start()- metoden. Dette vil oprette en ny tråd og begynde at udføre dens logik. Lad os se:

/**
* 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();
   }
}
Konsoloutputtet vil være noget som dette:
Tråd-1 Tråd-0 Tråd-2
Det vil sige, selv her ser vi, at tråde ikke udføres i rækkefølge, men snarere som JVM finder det passende til at køre dem :)

Implementer Runnable-grænsefladen

Hvis du er imod arv og/eller allerede arver en anden klasse, kan du bruge java.lang.Runnable- grænsefladen. Her får vi vores klasse til at implementere denne grænseflade ved at implementere run() metoden, ligesom i eksemplet ovenfor. Det eneste, der er tilbage, er at oprette trådobjekter . Det ser ud til, at flere linjer kode er værre. Men vi ved, hvor skadelig arv er, og at det er bedre at undgå det med alle midler ;) Tag et kig:

/**
* 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();
   }
}
Og her er resultatet:
Tråd-0 Tråd-1 Tråd-2

25. Hvad er forskellen mellem en proces og en tråd?

Top 50 jobsamtale spørgsmål og svar til Java Core.  Del 2 - 2En proces og en tråd er forskellige på følgende måder:
  1. Et kørende program kaldes en proces, men en tråd er en del af en proces.
  2. Processer er uafhængige, men tråde er dele af en proces.
  3. Processer har forskellige adresserum i hukommelsen, men tråde deler et fælles adresseområde.
  4. Kontekstskifte mellem tråde er hurtigere end at skifte mellem processer.
  5. Inter-proces kommunikation er langsommere og dyrere end inter-thread kommunikation.
  6. Eventuelle ændringer i en overordnet proces påvirker ikke en underordnet proces, men ændringer i en overordnet tråd kan påvirke en undertråd.

26. Hvad er fordelene ved multithreading?

  1. Multithreading gør det muligt for et program/program altid at være lydhør over for input, selvom det allerede kører nogle baggrundsopgaver;
  2. Multithreading gør det muligt at udføre opgaver hurtigere, fordi tråde kører uafhængigt;
  3. Multithreading giver bedre brug af cachehukommelse, fordi tråde kan få adgang til delte hukommelsesressourcer;
  4. Multithreading reducerer antallet af krævede servere, fordi en server kan køre flere tråde samtidigt.

27. Hvad er tilstandene i en tråds livscyklus?

Top 50 jobsamtale spørgsmål og svar til Java Core.  Del 2 - 3
  1. Ny: I denne tilstand oprettes Thread- objektet ved hjælp af den nye operator, men en ny tråd eksisterer endnu ikke. Tråden starter ikke før vi kalder start() metoden.
  2. Kørbar: I denne tilstand er tråden klar til at køre efter start() metode kaldes. Den er dog endnu ikke blevet valgt af trådplanlæggeren.
  3. Kører: I denne tilstand vælger trådplanlæggeren en tråd fra en klar-tilstand, og den kører.
  4. Venter/blokeret: i denne tilstand kører en tråd ikke, men den er stadig i live eller venter på, at en anden tråd afsluttes.
  5. Dead/Terminated: Når en tråd afslutter run()- metoden, er den i en død eller afsluttet tilstand.

28. Er det muligt at køre en tråd to gange?

Nej, vi kan ikke genstarte en tråd, for efter at en tråd er startet og kører, går den i Død tilstand. Hvis vi prøver at starte en tråd to gange, vil en java.lang.IllegalThreadStateException blive kastet. Lad os se:

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();
   }
}
Der vil være en undtagelse, så snart udførelsen kommer til den anden start af den samme tråd. Prøv det selv ;) Det er bedre at se dette en gang end at høre om det hundrede gange.

29. Hvad hvis du kalder run() direkte uden at kalde start()?

Ja, du kan helt sikkert kalde run()- metoden, men en ny tråd vil ikke blive oprettet, og metoden vil ikke køre på en separat tråd. I dette tilfælde har vi et almindeligt objekt, der kalder en almindelig metode. Hvis vi taler om start()- metoden, så er det en anden sag. Når denne metode kaldes, starter JVM en ny tråd. Denne tråd kalder til gengæld vores metode ;) Tror du ikke på det? Her, prøv det:

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();
   }
}
Og konsoludgangen vil se sådan ud:
0123401234
Som du kan se, blev der ikke oprettet nogen tråd. Alt fungerede ligesom i en almindelig klasse. Først blev metoden for det første objekt udført, og derefter det andet.

30. Hvad er en dæmontråd?

En dæmontråd er en tråd, der udfører opgaver med en lavere prioritet end en anden tråd. Med andre ord er dens opgave at udføre hjælpeopgaver, der kun skal udføres i forbindelse med en anden (hoved) tråd. Der er mange dæmontråde, der kører automatisk, såsom affaldsopsamling, færdiggører osv.

Hvorfor afslutter Java en dæmontråd?

Dæmon-trådens eneste formål er at give baggrundsstøtte til en brugers tråd. Følgelig, hvis hovedtråden afsluttes, afslutter JVM automatisk alle sine daemon-tråde.

Metoder i trådklassen

Java.lang.Thread - klassen giver to metoder til at arbejde med en dæmontråd:
  1. public void setDaemon(boolesk status) — Denne metode angiver, om dette vil være en dæmontråd. Standarden er falsk . Det betyder, at der ikke oprettes nogen dæmontråde, medmindre du specifikt siger det.
  2. public boolean isDaemon() — Denne metode er i det væsentlige en getter for daemon- variablen, som vi indstillede ved hjælp af den forrige metode.
Eksempel:

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();
   }
}
Konsoludgang:
dæmon? ægte dæmon? falsk dæmon? falsk daemon tråd bruger tråd bruger tråd
Fra outputtet ser vi, at vi inde i selve tråden kan bruge den statiske currentThread() metode til at finde ud af hvilken tråd det er. Alternativt, hvis vi har en reference til trådobjektet, kan vi også finde ud af det direkte fra det. Dette giver det nødvendige niveau af konfigurerbarhed.

31. Er det muligt at gøre en tråd til en dæmon efter den er blevet oprettet?

Nej. Hvis du forsøger at gøre dette, får du en IllegalThreadStateException . Det betyder, at vi kun kan oprette en dæmontråd, før den starter. Eksempel:

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

32. Hvad er en shutdown krog?

En shutdown hook er en tråd, der implicit kaldes, før Java Virtual Machine (JVM) lukkes ned. Således kan vi bruge den til at frigive en ressource eller gemmetilstand, når den virtuelle Java-maskine lukker ned normalt eller unormalt. Vi kan tilføje en shutdown krog ved at bruge følgende metode:

Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
Som vist i eksemplet:

/**
* 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();
       }
   }
}
Konsoludgang:
Nu skal programmet falde i søvn. Tryk på Ctrl+C for at afslutte det. shutdown krog udført

33. Hvad er synkronisering?

I Java er synkronisering muligheden for at kontrollere flere trådes adgang til enhver delt ressource. Når flere tråde forsøger at udføre den samme opgave samtidigt, kan du få et forkert resultat. For at løse dette problem bruger Java synkronisering, som tillader kun at køre én tråd ad gangen. Synkronisering kan opnås på tre måder:
  • Synkronisering af en metode
  • Synkronisering af en bestemt blok
  • Statisk synkronisering

Synkronisering af en metode

En synkroniseret metode bruges til at låse et objekt for enhver delt ressource. Når en tråd kalder en synkroniseret metode, erhverver den automatisk objektets lås og frigiver den, når tråden fuldfører sin opgave. For at få dette til at fungere, skal du tilføje det synkroniserede søgeord. Vi kan se, hvordan dette fungerer ved at se på et eksempel:

/**
* 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);
   }
}
Og konsoludgangen er denne:
I Tråd-0 Skriv et Brev Jeg Tråder Ikke-1 Skriv ikke noget bogstav

Synkroniseringsblok

En synkroniseret blok kan bruges til at udføre synkronisering på en hvilken som helst bestemt ressource i en metode. Lad os sige, at i en stor metode (ja, du skal ikke skrive dem, men nogle gange sker de) skal du af en eller anden grund kun synkronisere en lille sektion. Hvis du lægger al metodens kode i en synkroniseret blok, vil den fungere på samme måde som en synkroniseret metode. Syntaksen ser sådan ud:

synchronized ("object to be locked") {
   // The code that must be protected
}
For at undgå at gentage det foregående eksempel, vil vi oprette tråde ved hjælp af anonyme klasser, dvs. vi vil straks implementere Runnable-grænsefladen.

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

}
Og konsoludgangen er denne:
I Writer1 Write A Letter I Don't Writer2 Skriver ikke noget bogstav

Statisk synkronisering

Hvis du laver en statisk metode synkroniseret, vil låsningen ske på klassen, ikke objektet. I dette eksempel udfører vi statisk synkronisering ved at anvende det synkroniserede nøgleord til en statisk metode:

/**
* 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();
   }
}
Og konsoludgangen er denne:
I Don't Writer2 Skriver ikke noget bogstav I Writer1 Write A Letter

34. Hvad er en flygtig variabel?

I multithreaded programmering bruges det flygtige nøgleord til trådsikkerhed. Når en variabel variabel er ændret, er ændringen synlig for alle andre tråde, så en variabel kan bruges af én tråd ad gangen. Ved at bruge det flygtige nøgleord kan du garantere, at en variabel er trådsikker og gemt i delt hukommelse, og at tråde ikke gemmer den i deres cache. Hvordan ser det her ud?

private volatile AtomicInteger count;
Vi tilføjer blot volatile til variablen. Men husk på, at dette ikke betyder fuldstændig trådsikkerhed... Operationer på variablen er måske ikke atomare. Når det er sagt, kan du bruge Atomic- klasser, der udfører operationer atomisk, altså i en enkelt CPU-instruktion. Der er mange sådanne klasser i java.util.concurrent.atomic -pakken.

35. Hvad er dødvande?

I Java er deadlock noget, der kan ske som en del af multithreading. En deadlock kan opstå, når en tråd venter på et objekts lås erhvervet af en anden tråd, og den anden tråd venter på objektets lås erhvervet af den første tråd. Det betyder, at de to tråde venter på hinanden, og eksekveringen af ​​deres kode kan ikke fortsætte. Top 50 jobsamtale spørgsmål og svar til Java Core.  Del 2 - 4Lad os overveje et eksempel, der har en klasse, der implementerer Runnable. Dens konstruktør tager to ressourcer. Metoden run() henter låsen til dem i rækkefølge. Hvis du opretter to objekter af denne klasse og sender ressourcerne i en anden rækkefølge, så kan du nemt løbe ind i dødvande:

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);
           }
       }
   }
}
Konsoludgang:
Den første tråd erhvervede den første ressource Den anden tråd erhvervede den anden ressource

36. Hvordan undgår du dødvande?

Fordi vi ved, hvordan dødvande opstår, kan vi drage nogle konklusioner...
  • I eksemplet ovenfor opstår dødvandet på grund af, at vi har indlejret låsning. Det vil sige, at vi har en synkroniseret blok inde i en synkroniseret blok. For at undgå dette skal du i stedet for indlejring oprette et nyt højere abstraktionslag, flytte synkroniseringen til det højere niveau og eliminere den indlejrede låsning.
  • Jo mere du låser, jo mere sandsynligt er der en dødvande. Derfor skal du, hver gang du tilføjer en synkroniseret blok, tænke over, om du virkelig har brug for den, og om du kan undgå at tilføje en ny.
  • Brug af Thread.join() . Du kan også løbe i dødvande, mens en tråd venter på en anden. For at undgå dette problem kan du overveje at indstille en timeout for join() -metoden.
  • Hvis vi har én tråd, så er der ingen dødvande ;)

37. Hvad er en race tilstand?

Hvis virkelige løb involverer biler, så involverer løb i multithreading tråde. Men hvorfor? :/ Der er to tråde der kører og kan få adgang til det samme objekt. Og de kan forsøge at opdatere det delte objekts tilstand på samme tid. Alt er klart indtil videre, ikke? Tråde udføres enten bogstaveligt parallelt (hvis processoren har mere end én kerne) eller sekventielt, hvor processoren allokerer interleaved tidssnit. Vi kan ikke styre disse processer. Det betyder, at når en tråd læser data fra et objekt, kan vi ikke garantere, at den har tid til at ændre objektet, FØR en anden tråd gør det. Sådanne problemer opstår, når vi har disse "check-and-act"-kombinationer. Hvad betyder det? Antag, at vi har en hvis- sætning, hvis krop ændrer selve hvis-betingelsen, for eksempel:

int z = 0;

// Check
if (z < 5) {
// Act
   z = z + 5;
}
To tråde kunne indtaste denne kodeblok samtidigt, når z stadig er nul, og så kunne begge tråde ændre dens værdi. Som et resultat vil vi ikke få den forventede værdi på 5. I stedet ville vi få 10. Hvordan undgår du dette? Du skal anskaffe en lås, før du kontrollerer og handler, og frigør derefter låsen bagefter. Det vil sige, at du skal have den første tråd til at indtaste if- blokken, udføre alle handlingerne, ændre z , og først derefter give den næste tråd mulighed for at gøre det samme. Men den næste tråd kommer ikke ind i if- blokken, da z nu vil være 5:

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

I stedet for en konklusion

Jeg vil gerne sige tak til alle, der læste til slutningen. Det var langt, men du holdt ud! Måske er ikke alt klart. Dette er normalt. Da jeg først begyndte at studere Java, kunne jeg ikke pakke min hjerne rundt om, hvad en statisk variabel er. Men ingen big deal. Jeg sov på det, læste et par kilder mere, og så kom forståelsen. Forberedelse til en samtale er mere et akademisk spørgsmål snarere end et praktisk spørgsmål. Som et resultat bør du før hvert interview gennemgå og genopfriske i din hukommelse de ting, som du måske ikke bruger ret ofte.

Og som altid er her nogle nyttige links:

Tak til jer alle, fordi I læste med. Vi ses snart :) Min GitHub profilTop 50 jobsamtale spørgsmål og svar til Java Core.  Del 2 - 5