CodeGym /Java Blog /Willekeurig /Multithreading in Java
John Squirrels
Niveau 41
San Francisco

Multithreading in Java

Gepubliceerd in de groep Willekeurig
Hoi! Allereerst gefeliciteerd: je hebt het onderwerp Multithreading in Java bereikt! Dit is een serieuze prestatie - je hebt een lange weg afgelegd. Maar bereid je voor: dit is een van de moeilijkste onderwerpen in de cursus. En het is niet zo dat we hier complexe klassen of veel methoden gebruiken: in feite gebruiken we er minder dan twintig. Het is meer dat je je manier van denken een beetje moet veranderen. Voorheen werden uw programma's opeenvolgend uitgevoerd. Sommige regels code kwamen na andere, sommige methoden kwamen na andere, en alles was in wezen duidelijk. Eerst hebben we iets berekend, vervolgens het resultaat op de console weergegeven en toen eindigde het programma. Om multithreading te begrijpen, is het beter om in termen van parallellisme te denken. Laten we beginnen met iets heel eenvoudigs: ) Stel je voor dat je gezin van het ene huis naar het andere verhuist. Het verzamelen van al uw boeken zal een belangrijk onderdeel van de verhuizing zijn. Je hebt veel boeken verzameld en je moet ze in dozen stoppen. Momenteel ben jij de enige die beschikbaar is. Mam maakt eten klaar, broer pakt kleren in en zus ging naar de winkel. Alleen kun je het op de een of andere manier redden. Vroeg of laat voltooi je de taak zelf, maar het kost veel tijd. Je zus komt echter over 20 minuten terug van de winkel en ze heeft niets anders te doen. Zodat ze mee kan doen. De taak is niet veranderd: boeken in dozen stoppen. Maar het wordt twee keer zo snel uitgevoerd. Waarom? Omdat het werk parallel gebeurt. Twee verschillende 'threads' (jij en je zus) voeren tegelijkertijd dezelfde taak uit. En als er niets verandert, dan ontstaat er een enorm tijdsverschil met de situatie waarin je alles alleen doet. Als broer binnenkort klaar is met zijn werk, kan hij je helpen en gaat het nog sneller.

Problemen opgelost door multithreading

Multithreading is eigenlijk uitgevonden om twee belangrijke doelen te bereiken:
  1. Doe meerdere dingen tegelijk.

    In het bovenstaande voorbeeld voerden verschillende threads (gezinsleden) verschillende acties parallel uit: ze deden de afwas, gingen naar de winkel en pakten dingen in.

    We kunnen een voorbeeld geven dat nauwer verband houdt met programmeren. Stel je hebt een programma met een gebruikersinterface. Wanneer u in het programma op 'Doorgaan' klikt, moeten er enkele berekeningen worden uitgevoerd en zou de gebruiker het volgende scherm moeten zien. Als deze acties opeenvolgend zouden worden uitgevoerd, zou het programma gewoon blijven hangen nadat de gebruiker op de knop 'Doorgaan' klikt. De gebruiker krijgt het scherm met de knop 'Doorgaan' te zien totdat het programma alle interne berekeningen heeft uitgevoerd en het gedeelte bereikt waar de gebruikersinterface wordt vernieuwd.

    Nou, ik denk dat we een paar minuten wachten!

    Multithreading in Java: wat het is, de voordelen en veelvoorkomende valkuilen - 3

    Of we kunnen ons programma herwerken, of, zoals programmeurs zeggen, 'paralleliseren'. Laten we onze berekeningen op één thread uitvoeren en de gebruikersinterface op een andere tekenen. De meeste computers hebben genoeg middelen om dit te doen. Als we deze route volgen, zal het programma niet vastlopen en zal de gebruiker soepel tussen schermen bewegen zonder zich zorgen te maken over wat er binnen gebeurt. Het een staat het ander niet in de weg :)

  2. Berekeningen sneller uitvoeren.

    Alles is hier veel eenvoudiger. Als onze processor meerdere cores heeft, en de meeste processors hebben dat tegenwoordig, dan kunnen meerdere cores onze takenlijst parallel aan. Het is duidelijk dat als we 1000 taken moeten uitvoeren en elk een seconde duurt, één kern de lijst in 1000 seconden kan voltooien, twee kernen in 500 seconden, drie in iets meer dan 333 seconden, enz.

Maar zoals je in deze les al hebt gelezen, zijn de systemen van vandaag erg slim, en zelfs op één rekenkern zijn ze in staat om parallellisme te bereiken, of beter gezegd pseudo-parallelisme, waarbij taken afwisselend worden uitgevoerd. Laten we algemeenheden naar details verplaatsen en de belangrijkste klasse in de Java multithreading-bibliotheek leren kennen: java.lang.Thread. Strikt genomen worden Java-threads weergegeven door instanties van de klasse Thread . Dit betekent dat om 10 threads te maken en uit te voeren, je 10 instanties van deze klasse nodig hebt. Laten we het eenvoudigste voorbeeld schrijven:

public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("I'm Thread! My name is " + getName());
   }
}
Om threads te maken en uit te voeren, moeten we een klasse maken, deze de java.lang laten erven . Thread- klasse en overschrijft de methode run() ervan. Die laatste eis is heel belangrijk. Het is in de methode run() dat we de logica definiëren voor onze thread om uit te voeren. Als we nu een instantie van MyFirstThread maken en uitvoeren, geeft de methode run() een regel met een naam weer: de methode getName() geeft de 'systeem'-naam van de thread weer, die automatisch wordt toegewezen. Maar waarom spreken we voorzichtig? Laten we er een maken en ontdekken!

public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
Console-uitvoer: ik ben Thread! Mijn naam is Draad-2 Ik ben Draad! Mijn naam is Draad-1 Ik ben Draad! Mijn naam is Draad-0 Ik ben Draad! Mijn naam is Draad-3 Ik ben Draad! Mijn naam is Draad-6 Ik ben Draad! Mijn naam is Draad-7 Ik ben Draad! Mijn naam is Draad-4 Ik ben Draad! Mijn naam is Draad-5 Ik ben Draad! Mijn naam is Draad-9 Ik ben Draad! Mijn naam is Thread-8. Laten we 10 threads maken ( MyFirstThread- objecten, die Thread erven ) en ze starten door de start()- methode op elk object aan te roepen. Nadat de methode start() is aangeroepen , wordt de logica in de methode run() uitgevoerd. Let op: de draadnamen staan ​​niet op volgorde. Het is raar dat ze niet opeenvolgend waren:, Draad-1 , Draad-2 , enzovoort? Toevallig is dit een voorbeeld van een tijd waarin 'sequentieel' denken niet past. Het probleem is dat we alleen opdrachten hebben gegeven om 10 threads te maken en uit te voeren. De threadplanner, een speciaal mechanisme van het besturingssysteem, bepaalt hun uitvoeringsvolgorde. Het precieze ontwerp en de besluitvormingsstrategie zijn onderwerpen voor een diepgaande discussie waar we nu niet op ingaan. Het belangrijkste om te onthouden is dat de programmeur de uitvoeringsvolgorde van threads niet kan bepalen. Probeer de methode main() in het bovenstaande voorbeeld nog een paar keer uit te voeren om de ernst van de situatie te begrijpen. Console-uitvoer bij tweede uitvoering: Ik ben draad! Mijn naam is Draad-0 Ik ben Draad! Mijn naam is Draad-4 Ik ben Draad! Mijn naam is Draad-3 Ik ben Draad! Mijn naam is Draad-2 Ik ben Draad! Mijn naam is Draad-1 Ik ben Draad! Mijn naam is Draad-5 Ik ben Draad! Mijn naam is Draad-6 Ik ben Draad! Mijn naam is Draad-8 Ik ben Draad! Mijn naam is Draad-9 Ik ben Draad! Mijn naam is Thread-7 Console-uitvoer van de derde run: ik ben Thread! Mijn naam is Draad-0 Ik ben Draad! Mijn naam is Draad-3 Ik ben Draad! Mijn naam is Draad-1 Ik ben Draad! Mijn naam is Draad-2 Ik ben Draad! Mijn naam is Draad-6 Ik ben Draad! Mijn naam is Draad-4 Ik ben Draad! Mijn naam is Draad-9 Ik ben Draad! Mijn naam is Draad-5 Ik ben Draad! Mijn naam is Draad-7 Ik ben Draad! Mijn naam is Draad-8

Problemen veroorzaakt door multithreading

In ons voorbeeld met boeken zag je dat multithreading zeer belangrijke taken oplost en onze programma's sneller kan maken. Vaak vele malen sneller. Maar multithreading wordt als een moeilijk onderwerp beschouwd. Als het verkeerd wordt gebruikt, creëert het inderdaad problemen in plaats van ze op te lossen. Als ik zeg 'creëert problemen', bedoel ik niet in een of andere abstracte zin. Er zijn twee specifieke problemen die multithreading kan veroorzaken: impasse en race-omstandigheden. Deadlock is een situatie waarin meerdere threads wachten op bronnen die door elkaar worden vastgehouden en geen van hen kan blijven draaien. We zullen er in de volgende lessen meer over vertellen. Het volgende voorbeeld is voor nu voldoende: Multithreading in Java: wat het is, de voordelen en veelvoorkomende valkuilen - 4Stel je voor dat Thread-1 interageert met een Object-1, en dat Thread-2 interageert met Object-2. Verder is het programma zo geschreven dat:
  1. Thread-1 stopt met communiceren met Object-1 en schakelt over naar Object-2 zodra Thread-2 stopt met communiceren met Object-2 en overschakelt naar Object-1.
  2. Thread-2 stopt met communiceren met Object-2 en schakelt over naar Object-1 zodra Thread-1 stopt met communiceren met Object-1 en overschakelt naar Object-2.
Zelfs zonder een grondig begrip van multithreading, kunt u gemakkelijk zien dat er niets zal gebeuren. De threads zullen nooit van plaats wisselen en zullen voor altijd op elkaar wachten. De fout lijkt voor de hand liggend, maar is dat in werkelijkheid niet. Dit doe je eenvoudig in een programma. In volgende lessen zullen we voorbeelden bekijken van code die een impasse veroorzaakt. Trouwens, Quora heeft een geweldig real-life voorbeeld dat uitlegt wat een impasse isis. 'In sommige staten in India verkopen ze je geen landbouwgrond tenzij je een geregistreerde boer bent. Ze schrijven je echter niet in als boer als je geen landbouwgrond bezit'. Geweldig! Wat kunnen we zeggen?! :) Laten we het nu hebben over raceomstandigheden. Een raceconditie is een ontwerpfout in een multithreaded systeem of applicatie, waarbij de werking van het systeem of de applicatie afhangt van de volgorde waarin delen van de code worden uitgevoerd. Onthoud ons voorbeeld waarin we discussies begonnen:

public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("Thread executed: " + getName());
   }
}

public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
Stel je nu voor dat het programma verantwoordelijk is voor het besturen van een robot die voedsel kookt! Thread-0 haalt eieren uit de koelkast. Draad-1 zet het fornuis aan. Draad-2 pakt een pan en zet die op het fornuis. Draad-3 steekt de kachel aan. Thread-4 giet olie in de pan. Draad-5 breekt de eieren en giet ze in de pan. Draad-6 gooit de eierschalen in de prullenbak. Thread-7 haalt de gekookte eieren van de brander. Thread-8 legt de gekookte eieren op een bord. Draad-9 wast de afwas. Kijk naar de resultaten van ons programma: Thread uitgevoerd: Thread-0 Thread uitgevoerd: Thread-2 Thread uitgevoerd Thread-1 Thread uitgevoerd: Thread-4 Thread uitgevoerd: Thread-9 Thread uitgevoerd: Thread-5 Thread uitgevoerd: Thread-8 Thread uitgevoerd: Thread-7 Thread uitgevoerd: Thread-3 Is dit een komische routine? :) En dat allemaal omdat het werk van ons programma afhangt van de uitvoeringsvolgorde van de threads. Bij de minste overtreding van de vereiste volgorde verandert onze keuken in een hel en vernietigt een krankzinnige robot alles eromheen. Dit is ook een veelvoorkomend probleem bij multithreaded programmeren. Je hoort er meer dan eens over. Ter afsluiting van deze les zou ik een boek over multithreading willen aanbevelen. Multithreading in Java: wat het is, de voordelen en veelvoorkomende valkuilen - 6'Java Concurrency in Practice' is geschreven in 2006, maar heeft zijn relevantie niet verloren. Het is gewijd aan multithreaded Java-programmering - van de basis tot de meest voorkomende fouten en antipatronen. Als je op een dag besluit een multithreading-goeroe te worden, is dit boek een must-read. Tot ziens in de volgende lessen! :)
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION