Problemen opgelost door multithreading
Multithreading is eigenlijk uitgevonden om twee belangrijke doelen te bereiken:-
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!
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 :)
-
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.
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: Stel 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:- 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.
- 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.
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. '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! :)
GO TO FULL VERSION