Java-samtalespørsmål om samtidighet (+ svar)

Denne artikkelen er en del av en serie: • Java Collections Interview Questions

• Java Type System Interview Questions

• Java Concurrency Interview Questions (+ Answers) (nåværende artikkel) • Java Class Structure and Initialization Interview Questions

• Java 8 intervjuspørsmål (+ svar)

• Minnehåndtering i Java-intervjuspørsmål (+ svar)

• Java Generics intervjuspørsmål (+ svar)

• Intervju med Java Flow Control (+ svar)

• Java-unntaksspørsmål (+ svar)

• Spørsmål om Java-merknader (+ svar)

• Spørsmål om topp vårrammeverk

1. Introduksjon

Samtidighet i Java er et av de mest komplekse og avanserte emnene som ble tatt opp under tekniske intervjuer. Denne artikkelen gir svar på noen av intervjuspørsmålene om emnet du kan støte på.

Q1. Hva er forskjellen mellom en prosess og en tråd?

Både prosesser og tråder er enheter av samtidighet, men de har en grunnleggende forskjell: prosesser deler ikke et felles minne, mens tråder gjør det.

Fra operativsystemets synspunkt er en prosess en uavhengig programvare som kjører i sitt eget virtuelle minneplass. Ethvert multitasking-operativsystem (som betyr nesten ethvert moderne operativsystem) må skille prosesser i minnet slik at en mislykket prosess ikke vil dra alle andre prosesser ned ved å kryptere vanlig minne.

Prosessene blir således vanligvis isolert, og de samarbeider ved hjelp av kommunikasjon mellom prosesser som er definert av operativsystemet som en slags mellomliggende API.

Tvert imot, en tråd er en del av et program som deler et felles minne med andre tråder i samme applikasjon. Ved å bruke felles minne kan du barbere mye overhead, designe trådene for å samarbeide og utveksle data mellom dem mye raskere.

Q2. Hvordan kan du lage en trådforekomst og kjøre den?

For å opprette en forekomst av en tråd, har du to alternativer. Først passerer en Kjørbar instans til sin konstruktør og ring start(). Kjørbar er et funksjonelt grensesnitt, så det kan overføres som et lambdauttrykk:

Tråd tråd1 = ny tråd (() -> System.out.println ("Hello World from Runnable!")); thread1.start ();

Tråden implementerer også Kjørbar, så en annen måte å starte en tråd på er å opprette en anonym underklasse, overstyre dens løpe() metode, og ring deretter start():

Tråd thread2 = ny tråd () {@Override public void run () {System.out.println ("Hello World from subclass!"); }}; thread2.start ();

Q3. Beskriv de forskjellige statene til en tråd og når oppstår statlige overganger.

Tilstanden til en Tråd kan kontrolleres ved hjelp av Thread.getState () metode. Ulike tilstander av en Tråd er beskrevet i Tråd. Stat enum. De er:

  • NY - en ny Tråd forekomst som ennå ikke ble startet via Thread.start ()
  • RUNNABLE - en løpende tråd. Det kalles kjørbart fordi det til enhver tid kan løpe eller vente på neste kvantum fra trådplanleggeren. EN NY tråden kommer inn i RUNNABLE oppgi når du ringer Thread.start () på den
  • BLOKERT - en løpende tråd blir blokkert hvis den trenger å gå inn i en synkronisert seksjon, men kan ikke gjøre det på grunn av en annen tråd som holder skjermen til denne seksjonen
  • VENTER - en tråd går inn i denne tilstanden hvis den venter på at en annen tråd skal utføre en bestemt handling. For eksempel går en tråd inn i denne tilstanden når den ringer Object.wait () metoden på en skjerm den holder, eller Thread.join () metode på en annen tråd
  • TIMED_WAITING - samme som ovenfor, men en tråd kommer inn i denne tilstanden etter å ha kalt tidsbestemte versjoner av Thread.sleep (), Object.wait (), Thread.join () og noen andre metoder
  • AVSLUTTET - en tråd har fullført utførelsen av den Runnable.run () metode og avsluttet

Q4. Hva er forskjellen mellom kjørbare og kallbare grensesnitt? Hvordan brukes de?

De Kjørbar grensesnittet har en enkelt løpe metode. Den representerer en beregningsenhet som må kjøres i en egen tråd. De Kjørbar grensesnittet tillater ikke at denne metoden returnerer verdi eller kaster unchecked unntak.

De Kan kalles grensesnittet har en enkelt anrop metode og representerer en oppgave som har en verdi. Det er derfor anrop metoden returnerer en verdi. Det kan også kaste unntak. Kan kalles brukes vanligvis i ExecutorService tilfeller for å starte en asynkron oppgave og deretter ringe den returnerte Framtid forekomst for å få verdien.

Q5. Hva er en Daemon-tråd, og hva er bruken av tilfeller? Hvordan kan du lage en Daemon-tråd?

En daemon-tråd er en tråd som ikke hindrer JVM i å gå ut. Når alle ikke-demon-tråder avsluttes, forlater JVM ganske enkelt alle gjenværende daemon-tråder. Daemon-tråder brukes vanligvis til å utføre noen støtte- eller serviceoppgaver for andre tråder, men du bør ta i betraktning at de kan bli forlatt når som helst.

For å starte en tråd som en demon, bør du bruke setDaemon () metoden før du ringer start():

Thread daemon = new Thread (() -> System.out.println ("Hello from daemon!")); daemon.setDaemon (true); daemon.start ();

Merkelig, hvis du kjører dette som en del av hoved() metode, blir meldingen kanskje ikke skrevet ut. Dette kan skje hvis hoved() tråden ville avsluttes før demonen skulle komme til å skrive ut meldingen. Du bør generelt ikke gjøre noen I / O i demon-tråder, da de ikke engang vil kunne utføre dem endelig blokkerer og lukker ressursene hvis de blir forlatt.

Q6. Hva er trådens avbruddsflagg? Hvordan kan du stille inn og kontrollere det? Hvordan forholder det seg til avbruddsopptaket?

Avbruddsflagget, eller avbruddsstatusen, er et internt Tråd flagg som settes når tråden blir avbrutt. For å stille det, ring bare thread.interrupt () på trådobjektet.

Hvis en tråd er for tiden inne i en av metodene som kaster InterruptedException (vente, bli med, søvn osv.), kaster denne metoden umiddelbart InterruptedException. Tråden er fri til å behandle dette unntaket i henhold til sin egen logikk.

Hvis en tråd ikke er inne i en slik metode og thread.interrupt () kalles, skjer det ikke noe spesielt. Det er trådens ansvar å regelmessig kontrollere avbruddsstatus ved hjelp av statisk tråd. avbrutt () eller forekomst isInterrupted () metode. Forskjellen mellom disse metodene er at statisk tråd. avbrutt () tømmer avbruddsflagget, mens isInterrupted () gjør ikke.

Q7. Hva er Executor and Executorservice? Hva er forskjellen mellom disse grensesnittene?

Leder og ExecutorService er to relaterte grensesnitt av java.util.concurrent rammeverk. Leder er et veldig enkelt grensesnitt med en enkelt henrette metode akseptere Kjørbar tilfeller for utførelse. I de fleste tilfeller er dette grensesnittet som din oppgaveutførende kode skal avhenge av.

ExecutorService utvider Leder grensesnitt med flere metoder for håndtering og kontroll av livssyklusen til en samtidig oppgavetjeneste (avslutning av oppgaver i tilfelle avstenging) og metoder for mer kompleks asynkron oppgavebehandling inkludert Fremtid.

For mer informasjon om bruk Leder og ExecutorService, se artikkelen En guide til Java ExecutorService.

Q8. Hva er de tilgjengelige implementeringene av Executorservice i standardbiblioteket?

De ExecutorService grensesnittet har tre standardimplementeringer:

  • ThreadPoolExecutor - for å utføre oppgaver ved hjelp av en gruppe tråder. Når en tråd er ferdig med å utføre oppgaven, går den tilbake i bassenget. Hvis alle tråder i bassenget er opptatt, må oppgaven vente på sin tur.
  • ScheduledThreadPoolExecutor lar planlegge utførelse av oppgaver i stedet for å kjøre den umiddelbart når en tråd er tilgjengelig. Det kan også planlegge oppgaver med fast rente eller fast forsinkelse.
  • ForkJoinPool er en spesiell ExecutorService for å håndtere rekursive algoritmeroppgaver. Hvis du bruker en vanlig ThreadPoolExecutor for en rekursiv algoritme, vil du raskt oppdage at alle trådene dine er opptatt med å vente på at de lavere rekursjonsnivåene er ferdige. De ForkJoinPool implementerer den såkalte arbeids stjele algoritmen som gjør det mulig å bruke tilgjengelige tråder mer effektivt.

Q9. Hva er Java Memory Model (Jmm)? Beskriv dens formål og grunnleggende ideer.

Java Memory Model er en del av Java-språkspesifikasjonen beskrevet i kapittel 17.4. Den spesifiserer hvordan flere tråder får tilgang til vanlig minne i et samtidig Java-program, og hvordan dataendringer av en tråd blir synliggjort for andre tråder. Selv om det er ganske kort og konsist, kan JMM være vanskelig å forstå uten sterk matematisk bakgrunn.

Behovet for minnemodell oppstår fra det faktum at måten Java-koden din får tilgang til data ikke er hvordan den faktisk skjer på de lavere nivåene. Minne skriver og leser kan omorganiseres eller optimaliseres av Java kompilatoren, JIT kompilatoren og til og med CPU, så lenge det observerbare resultatet av disse leser og skriver er det samme.

Dette kan føre til mot-intuitive resultater når applikasjonen din skaleres til flere tråder fordi de fleste av disse optimaliseringene tar hensyn til en enkelt tråd for utførelse (kryss-trådoptimaliserene er fremdeles ekstremt vanskelige å implementere). Et annet stort problem er at minnet i moderne systemer er flerlags: flere kjerner til en prosessor kan beholde data som ikke skylles i cachene eller lese / skrive-buffere, noe som også påvirker minnetilstanden som er observert fra andre kjerner.

For å gjøre ting verre, ville eksistensen av forskjellige minnetilgangsarkitekturer bryte Java's løfte om å "skrive en gang, kjøre overalt". Heldigvis for programmererne, spesifiserer JMM noen garantier som du kan stole på når du designer flertrådede applikasjoner. Å holde seg til disse garantiene hjelper en programmerer å skrive flertrådet kode som er stabil og bærbar mellom forskjellige arkitekturer.

Hovedbegrepene for JMM er:

  • Handlinger, dette er inter-trådhandlinger som kan utføres av en tråd og oppdages av en annen tråd, som lese- eller skrivevariabler, låse / låse opp skjermer og så videre
  • Synkroniseringshandlinger, en viss delmengde av handlinger, som å lese / skrive a flyktige variabel, eller låse / låse opp en skjerm
  • Programordre (PO), den observerbare totale rekkefølgen av handlinger i en enkelt tråd
  • Synkroniseringsrekkefølge (SO), den totale rekkefølgen mellom alle synkroniseringshandlinger - den må være i samsvar med programrekkefølgen, det vil si at hvis to synkroniseringshandlinger kommer hverandre i PO, forekommer de i samme rekkefølge i SO
  • synkroniserer-med (SW) forhold mellom visse synkroniseringshandlinger, som å låse opp skjermen og låse den samme skjermen (i en annen eller samme tråd)
  • Hender før bestilling - kombinerer PO med SW (dette kalles transitiv lukking i mengdeori) for å lage en delvis ordning av alle handlinger mellom tråder. Hvis en handling skjer-før en annen, så kan resultatene av den første handlingen observeres av den andre handlingen (skriv for eksempel en variabel i en tråd og les den i en annen)
  • Skjer-før konsistens - et sett med handlinger er HB-konsistent hvis hver lesing observerer enten den siste skrivingen til den plasseringen i ordren som skjer før, eller annen skriving via dataløp
  • Henrettelse - et visst sett med ordnede handlinger og konsistensregler mellom dem

For et gitt program kan vi observere flere forskjellige henrettelser med forskjellige resultater. Men hvis et program er det riktig synkronisert, så ser alle henrettelsene ut til å være det sekvensielt konsekvent, noe som betyr at du kan resonnere om det flertrådede programmet som et sett med handlinger som skjer i en rekkefølge. Dette sparer deg bryet med å tenke på omordninger, optimaliseringer eller data caching under panseret.

Q10. Hva er et flyktig felt og hvilke garantier holder Jmm for slike felt?

EN flyktige feltet har spesielle egenskaper i henhold til Java Memory Model (se Q9). Leser og skriver om en flyktige variabel er synkroniseringshandlinger, noe som betyr at de har en total rekkefølge (alle trådene vil følge en konsekvent rekkefølge på disse handlingene). En avlesning av en flyktig variabel garanteres å observere den siste skrivingen til denne variabelen, i henhold til denne rekkefølgen.

Hvis du har et felt som er tilgjengelig fra flere tråder, med minst en tråd som skriver til det, bør du vurdere å lage det flyktige, ellers er det en liten garanti for hva en bestemt tråd vil lese fra dette feltet.

Nok en garanti for flyktige er atomicitet ved å skrive og lese 64-biters verdier (lang og dobbelt). Uten en flyktig modifikator, kan en lesning av et slikt felt observere en verdi som delvis er skrevet av en annen tråd.

Q11. Hvilke av de følgende operasjonene er atomare?

  • skrive til en ikke-flyktigeint;
  • skrive til en flyktig int;
  • skrive til en ikke-flyktig lang;
  • skrive til en flyktig lang;
  • økning a flyktig lang?

En skriv til en int (32-bit) variabel er garantert atomisk, uansett om den er flyktige eller ikke. EN lang (64-bit) variabel kan skrives i to separate trinn, for eksempel på 32-biters arkitekturer, så som standard er det ingen atomicitetsgaranti. Men hvis du spesifiserer flyktige modifikator, en lang variabel er garantert tilgang til atomisk.

Inkrementoperasjonen utføres vanligvis i flere trinn (å hente en verdi, endre den og skrive tilbake), så den garanteres aldri at den er atom, uansett om variabelen er flyktige eller ikke. Hvis du trenger å implementere atomøkning av en verdi, bør du bruke klasser AtomicInteger, AtomicLong etc.

Q12. Hvilke spesielle garantier holder Jmm for de siste feltene i en klasse?

JVM garanterer i utgangspunktet det endelig felt i en klasse vil initialiseres før noen tråd får tak i objektet. Uten denne garantien kan en referanse til et objekt bli publisert, dvs. bli synlig, til en annen tråd før alle feltene til dette objektet initialiseres på grunn av ombestillinger eller andre optimaliseringer. Dette kan føre til rask tilgang til disse feltene.

Dette er grunnen til at når du oppretter et uforanderlig objekt, bør du alltid lage alle feltene endelig, selv om de ikke er tilgjengelige via getter-metoder.

Q13. Hva er betydningen av et synkronisert nøkkelord i definisjonen av en metode? av en statisk metode? Før en blokk?

De synkronisert nøkkelord før en blokk betyr at en hvilken som helst tråd som går inn i denne blokken, må anskaffe skjermen (objektet i parentes). Hvis skjermen allerede er anskaffet av en annen tråd, vil den tidligere tråden gå inn i BLOKERT status og vent til skjermen slippes.

synkronisert (objekt) {// ...}

EN synkronisert forekomstmetoden har samme semantikk, men selve forekomsten fungerer som en skjerm.

synkronisert ugyldig forekomstMetode () {// ...}

For en statisk synkronisert metoden, er skjermen den Klasse objekt som representerer den erklærende klassen.

statisk synkronisert ugyldig staticMethod () {// ...}

Q14. Hvis to tråder kaller en synkronisert metode på forskjellige objektforekomster samtidig, kan en av disse trådene blokkere? Hva om metoden er statisk?

Hvis metoden er en forekomstmetode, fungerer forekomsten som en skjerm for metoden. To tråder som kaller metoden i forskjellige tilfeller skaffer forskjellige skjermer, så ingen av dem blir blokkert.

Hvis metoden er statisk, så er skjermen den Klasse gjenstand. For begge trådene er skjermen den samme, så en av dem vil sannsynligvis blokkere og vente på at en annen skal gå ut av synkronisert metode.

Q15. Hva er formålet med vente-, varslings- og varslingsmetodene i objektklassen?

En tråd som eier objektets skjerm (for eksempel en tråd som har kommet inn i en synkronisert seksjon beskyttet av objektet) kan ringe object.wait () å midlertidig frigjøre skjermen og gi andre tråder en sjanse til å skaffe skjermen. Dette kan for eksempel gjøres for å vente på en bestemt tilstand.

Når en annen tråd som kjøpte skjermen oppfyller betingelsen, kan den ringe objekt. varsle () eller object.notifyAll () og slipp skjermen. De gi beskjed metoden vekker en enkelt tråd i ventetilstand, og varsleAlle metoden vekker alle tråder som venter på denne skjermen, og de konkurrerer alle om å få tak i låsen.

Følgende BlockingQueue implementering viser hvordan flere tråder fungerer sammen via vent-varsle mønster. Hvis vi sette et element i en tom kø, alle tråder som ventet i ta metoden våkner og prøver å motta verdien. Hvis vi sette et element i en full kø, sette metode ventes for samtalen til metode. De metoden fjerner et element og varsler trådene som venter i sette metode at køen har et tomt sted for et nytt element.

offentlig klasse BlockingQueue {privat liste kø = ny LinkedList (); privat int-grense = 10; offentlig synkronisert void put (T-element) {while (queue.size () == limit) {try {wait (); } fange (InterruptedException e) {}} if (queue.isEmpty ()) {notifyAll (); } kø.tillegge (vare); } offentlig synkronisert T ta () kaster InterruptedException {mens (queue.isEmpty ()) {prøv {vent (); } fange (InterruptedException e) {}} if (queue.size () == limit) {notifyAll (); } retur kø. fjern (0); }}

Q16. Beskriv betingelsene for fastlåst, livelock og sult. Beskriv mulige årsaker til disse forholdene.

Dødlås er en tilstand i en gruppe tråder som ikke kan gjøre fremgang fordi hver tråd i gruppen må skaffe seg en ressurs som allerede er anskaffet av en annen tråd i gruppen. Det mest enkle tilfellet er når to tråder trenger å låse begge to ressursene for å komme videre, den første ressursen er allerede låst av en tråd, og den andre av en annen. Disse trådene vil aldri få en lås til begge ressursene og vil dermed aldri utvikle seg.

Livelock er et tilfelle av flere tråder som reagerer på forhold, eller hendelser, generert av seg selv. En hendelse skjer i en tråd og må behandles av en annen tråd.Under denne behandlingen skjer en ny hendelse som må behandles i første tråd, og så videre. Slike tråder er i live og ikke blokkert, men fremdeles gjør du ingen fremgang fordi de overvelder hverandre med ubrukelig arbeid.

Sult er et tilfelle av en tråd som ikke kan skaffe ressurs fordi andre tråder (eller tråder) okkuperer den for lenge eller har høyere prioritet. En tråd kan ikke gjøre fremgang og kan derfor ikke utføre nyttig arbeid.

Q17. Beskriv formålet og brukssakene til gaffelen / bli med.

Fork / join-rammeverket tillater parallellisering av rekursive algoritmer. Hovedproblemet med å parallellisere rekursjon ved hjelp av noe lignende ThreadPoolExecutor er at du raskt kan gå tom for tråder fordi hvert rekursive trinn vil kreve sin egen tråd, mens trådene opp i bunken vil være inaktiv og venter.

Inngangspunktet for gaffel / sammenføyning er ForkJoinPool klasse som er en implementering av ExecutorService. Den implementerer algoritmen for å stjele arbeid, der ledige tråder prøver å "stjele" arbeid fra travle tråder. Dette gjør det mulig å spre beregningene mellom forskjellige tråder og gjøre fremgang mens du bruker færre tråder enn det ville kreve med en vanlig trådgruppe.

Mer informasjon og kodeeksempler for gaffel / sammenføyningsrammeverket finner du i artikkelen “Guide to the Fork / Join Framework in Java”.

Neste » Java Class Structure and Initialization Interview Questions « Tidligere spørsmål om Java Type System-intervju

$config[zx-auto] not found$config[zx-overlay] not found