Multi-threaded code testen in Java

1. Inleiding

In deze zelfstudie behandelen we enkele basisprincipes van het testen van een gelijktijdig programma. We zullen ons primair richten op thread-based concurrency en de problemen die het oplevert bij het testen.

We zullen ook begrijpen hoe we sommige van deze problemen kunnen oplossen en multi-threaded code effectief kunnen testen in Java.

2. Gelijktijdig programmeren

Gelijktijdig programmeren verwijst naar programmeren waar we een groot deel van de berekening opsplitsen in kleinere, relatief onafhankelijke berekeningen.

De bedoeling van deze oefening is om deze kleinere berekeningen gelijktijdig uit te voeren, mogelijk zelfs parallel. Hoewel er verschillende manieren zijn om dit te bereiken, is het doel steevast om het programma sneller uit te voeren.

2.1. Threads en gelijktijdige programmering

Met processors die meer kernen dan ooit bevatten, staat gelijktijdige programmering voorop om deze efficiënt te benutten. Het blijft echter een feit gelijktijdige programma's zijn veel moeilijker te ontwerpen, schrijven, testen en onderhouden. Dus als we tenslotte effectieve en geautomatiseerde testcases kunnen schrijven voor gelijktijdige programma's, kunnen we een groot deel van deze problemen oplossen.

Dus, wat maakt het schrijven van tests voor gelijktijdige code zo moeilijk? Om dat te begrijpen, moeten we begrijpen hoe we gelijktijdigheid bereiken in onze programma's. Een van de meest populaire technieken voor gelijktijdig programmeren is het gebruik van threads.

Nu kunnen threads native zijn, in welk geval ze worden gepland door de onderliggende besturingssystemen. We kunnen ook gebruikmaken van zogenaamde groene threads, die rechtstreeks door een runtime worden gepland.

2.2. Moeilijkheden bij het testen van gelijktijdige programma's

Ongeacht het type threads dat we gebruiken, het is moeilijk om ze te gebruiken door thread-communicatie. Als het ons inderdaad lukt om een ​​programma te schrijven dat wel threads maar geen threadcommunicatie bevat, is er niets beters! Realistischer is dat threads meestal moeten communiceren. Er zijn twee manieren om dit te bereiken: gedeeld geheugen en het doorgeven van berichten.

Het grootste deel van de probleem in verband met gelijktijdige programmering ontstaat door het gebruik van native threads met gedeeld geheugen. Het testen van dergelijke programma's is om dezelfde redenen moeilijk. Meerdere threads met toegang tot gedeeld geheugen vereisen over het algemeen wederzijdse uitsluiting. We bereiken dit meestal door middel van een beveiligingsmechanisme met sloten.

Maar dit kan nog steeds leiden tot een groot aantal problemen, zoals race-omstandigheden, live-locks, deadlocks en uithongering van threads, om er maar een paar te noemen. Bovendien zijn deze problemen intermitterend, aangezien threadplanning in het geval van native threads volledig niet-deterministisch is.

Daarom is het schrijven van effectieve tests voor gelijktijdige programma's die deze problemen op een deterministische manier kunnen detecteren, inderdaad een uitdaging!

2.3. Anatomie van het tussenvoegen van draden

We weten dat native threads onvoorspelbaar kunnen worden gepland door besturingssystemen. Voor het geval deze threads toegang hebben tot gedeelde gegevens en deze wijzigen, het geeft aanleiding tot interessante thread-interleaving. Hoewel sommige van deze interleavings volledig acceptabel kunnen zijn, kunnen andere de uiteindelijke gegevens in een ongewenste staat achterlaten.

Laten we een voorbeeld nemen. Stel dat we een globale teller hebben die met elke thread wordt opgehoogd. Aan het einde van de verwerking willen we dat de staat van deze teller exact hetzelfde is als het aantal uitgevoerde threads:

privé int teller; public void increment () {counter ++; }

Nu naar het verhogen van een primitief geheel getal in Java is geen atomaire bewerking. Het bestaat uit het lezen van de waarde, het verhogen en uiteindelijk opslaan. Hoewel meerdere threads dezelfde bewerking uitvoeren, kan dit aanleiding geven tot veel mogelijke interleavings:

Hoewel deze specifieke interleaving volledig acceptabele resultaten oplevert, hoe zit het dan met deze:

Dit hadden we niet verwacht. Stel je nu voor dat honderden threads code uitvoeren die veel complexer is dan dit. Dit zal aanleiding geven tot onvoorstelbare manieren waarop de draden zullen verweven.

Er zijn verschillende manieren om code te schrijven die dit probleem vermijdt, maar dat is niet het onderwerp van deze tutorial. Synchronisatie met een slot is een van de meest voorkomende, maar het heeft zijn problemen met betrekking tot race-omstandigheden.

3. Multi-threaded code testen

Nu we de basisuitdagingen bij het testen van multi-threaded code begrijpen, zullen we zien hoe we ze kunnen overwinnen. We bouwen een eenvoudige use-case en proberen zoveel mogelijk problemen met betrekking tot gelijktijdigheid te simuleren.

Laten we beginnen met het definiëren van een eenvoudige klasse die mogelijk alles bijhoudt:

openbare klasse MyCounter {privé int count; public void increment () {int temp = count; aantal = temp + 1; } // Getter voor count}

Dit is een schijnbaar onschadelijk stukje code, maar het is niet moeilijk te begrijpen dat het niet thread-safe is. Als we toevallig een gelijktijdig programma met deze klasse schrijven, is het waarschijnlijk defect. Het doel van testen hier is om dergelijke defecten te identificeren.

3.1. Testen van niet-gelijktijdige onderdelen

Als vuistregel, het is altijd raadzaam om code te testen door deze te isoleren van gelijklopend gedrag. Dit is om redelijkerwijs vast te stellen dat er geen ander defect in de code is dat geen verband houdt met gelijktijdigheid. Laten we eens kijken hoe we dat kunnen doen:

@Test openbare ongeldige testCounter () {MyCounter-teller = nieuwe MyCounter (); for (int i = 0; i <500; i ++) {counter.increment (); } assertEquals (500, counter.getCount ()); }

Hoewel er hier niet veel aan de hand is, geeft deze test ons het vertrouwen dat het in ieder geval werkt als er geen concurrency is.

3.2. Eerste poging om te testen met gelijktijdigheid

Laten we doorgaan om dezelfde code opnieuw te testen, dit keer in een gelijktijdige setup. We zullen proberen toegang te krijgen tot dezelfde instantie van deze klasse met meerdere threads en kijken hoe deze zich gedraagt:

@Test openbare ongeldige testCounterWithConcurrency () gooit InterruptedException {int numberOfThreads = 10; ExecutorService service = Executors.newFixedThreadPool (10); CountDownLatch latch = nieuwe CountDownLatch (numberOfThreads); MyCounter counter = nieuwe MyCounter (); for (int i = 0; i {counter.increment (); latch.countDown ();}); } latch.await (); assertEquals (numberOfThreads, counter.getCount ()); }

Deze test is redelijk, omdat we proberen te werken met gedeelde gegevens met verschillende threads. Omdat we het aantal threads laag houden, zoals 10, zullen we merken dat het bijna altijd voorbijgaat. Interessant is dat als we beginnen met het verhogen van het aantal threads, laten we zeggen tot 100, zullen we zien dat de test meestal begint te mislukken.

3.3. Een betere poging om te testen met gelijktijdigheid

Hoewel uit de vorige test bleek dat onze code niet thread-safe is, is er een probleem met deze speen. Deze test is niet deterministisch omdat de onderliggende threads op een niet-deterministische manier door elkaar lopen. We kunnen echt niet op deze test vertrouwen voor ons programma.

Wat we nodig hebben is een manier om de interleaving van threads te controleren, zodat we problemen met gelijktijdigheid kunnen onthullen op een deterministische manier met veel minder threads. We beginnen met het aanpassen van de code die we aan het testen zijn:

openbare gesynchroniseerde ongeldige increment () gooit InterruptedException {int temp = count; wacht (100); aantal = temp + 1; }

Hier hebben we de methode gemaakt gesynchroniseerd en introduceerde een wachttijd tussen de twee stappen binnen de methode. De gesynchroniseerd trefwoord zorgt ervoor dat slechts één thread de tellen variabel tegelijk, en het wachten introduceert een vertraging tussen elke threaduitvoering.

Houd er rekening mee dat we de code die we willen testen niet noodzakelijkerwijs hoeven te wijzigen. Omdat er echter niet veel manieren zijn waarop we de threadplanning kunnen beïnvloeden, nemen we hier onze toevlucht tot.

In een later gedeelte zullen we zien hoe we dit kunnen doen zonder de code te wijzigen.

Laten we deze code nu op dezelfde manier testen als eerder:

@Test openbare ongeldige testSummationWithConcurrency () gooit InterruptedException {int numberOfThreads = 2; ExecutorService service = Executors.newFixedThreadPool (10); CountDownLatch latch = nieuwe CountDownLatch (numberOfThreads); MyCounter counter = nieuwe MyCounter (); for (int i = 0; ik {probeer {counter.increment ();} catch (InterruptedException e) {// Behandel uitzondering} latch.countDown ();}); } latch.await (); assertEquals (numberOfThreads, counter.getCount ()); }

Hier voeren we dit alleen uit met slechts twee threads, en de kans is groot dat we het defect dat we hebben gemist, kunnen achterhalen. Wat we hier hebben gedaan, is proberen een specifieke interleaving van draden te bereiken, waarvan we weten dat deze invloed op ons kan hebben. Hoewel goed voor de demonstratie, we vinden dit misschien niet handig voor praktische doeleinden.

4. Testhulpmiddelen beschikbaar

Naarmate het aantal threads toeneemt, groeit het mogelijke aantal manieren waarop ze kunnen worden verweven exponentieel. Haar gewoon niet mogelijk om al dergelijke interleavings te achterhalen en erop te testen. We moeten vertrouwen op tools om dezelfde of vergelijkbare inspanning voor ons te ondernemen. Gelukkig zijn er een paar beschikbaar om ons leven gemakkelijker te maken.

Er zijn twee brede categorieën tools beschikbaar voor het testen van gelijktijdige code. De eerste stelt ons in staat om een ​​redelijk hoge belasting te produceren op de gelijktijdige code met veel threads. Stress vergroot de kans op zeldzame interleaving en vergroot dus onze kans op het vinden van defecten.

De tweede stelt ons in staat om specifieke draadvervlechting te simuleren, waardoor we defecten met meer zekerheid kunnen vinden.

4.1. de tijd vliegt

De tempus-fugit Java-bibliotheek helpt ons om gemakkelijk gelijktijdige code te schrijven en te testen. We zullen ons hier concentreren op het testgedeelte van deze bibliotheek. We zagen eerder dat het produceren van stress op code met meerdere threads de kans vergroot op het vinden van defecten gerelateerd aan gelijktijdigheid.

Hoewel we hulpprogramma's kunnen schrijven om zelf de stress te produceren, biedt tempus-fugit handige manieren om hetzelfde te bereiken.

Laten we dezelfde code opnieuw bekijken waarvoor we eerder stress probeerden te produceren en begrijpen hoe we hetzelfde kunnen bereiken met tempus-fugit:

openbare klasse MyCounterTests {@Rule public ConcurrentRule gelijktijdig = new ConcurrentRule (); @Rule public RepeatingRule rule = nieuwe RepeatingRule (); privé statische MyCounter-teller = nieuwe MyCounter (); @Test @Concurrent (count = 10) @Repeating (herhaling = 10) openbare ongeldige runsMultipleTimes () {counter.increment (); } @AfterClass openbare statische leegte annotatedTestRunsMultipleTimes () gooit InterruptedException {assertEquals (counter.getCount (), 100); }}

Hier gebruiken we twee van de Regels beschikbaar voor ons van tempus-fugit. Deze regels onderscheppen de tests en helpen ons het gewenste gedrag toe te passen, zoals herhaling en gelijktijdigheid. Dus in feite herhalen we de bewerking die wordt getest tien keer, elk vanuit tien verschillende threads.

Naarmate we de herhaling en gelijktijdigheid vergroten, zullen onze kansen op het detecteren van defecten die verband houden met gelijktijdigheid toenemen.

4.2. Draad Weaver

Thread Weaver is in wezen een Java-framework voor het testen van multi-threaded code. We hebben eerder gezien dat het tussenvoegen van draden nogal onvoorspelbaar is, en daarom kunnen we bepaalde defecten nooit vinden door middel van regelmatige tests. Wat we effectief nodig hebben, is een manier om de interleaves te controleren en alle mogelijke interleaves te testen. Dit is bij onze vorige poging een vrij complexe taak gebleken.

Laten we eens kijken hoe Thread Weaver ons hier kan helpen. Thread Weaver stelt ons in staat om de uitvoering van twee afzonderlijke threads op een groot aantal manieren in elkaar te laten vallen, zonder dat we ons zorgen hoeven te maken over hoe. Het geeft ons ook de mogelijkheid om fijnmazige controle te hebben over hoe we willen dat de draden door elkaar heen lopen.

Laten we eens kijken hoe we onze vorige, naïeve poging kunnen verbeteren:

openbare klasse MyCounterTests {privé MyCounter-teller; @ThreadedBefore public void before () {counter = new MyCounter (); } @ThreadedMain openbare leegte mainThread () {counter.increment (); } @ThreadedSecondary openbare leegte secondThread () {counter.increment (); } @ThreadedAfter public void after () {assertEquals (2, counter.getCount ()); } @Test openbare ongeldige testCounter () {nieuwe AnnotatedTestRunner (). RunTests (this.getClass (), MyCounter.class); }}

Hier hebben we twee threads gedefinieerd die proberen onze teller te verhogen. Thread Weaver zal proberen deze test uit te voeren met deze threads in alle mogelijke interleaving-scenario's. Mogelijk krijgen we in een van de tussenbladen het defect, wat vrij duidelijk is in onze code.

4.3. MultithreadedTC

MultithreadedTC is nog een ander raamwerk voor het testen van gelijktijdige applicaties. Het beschikt over een metronoom die wordt gebruikt om fijne controle te geven over de opeenvolging van activiteiten in meerdere threads. Het ondersteunt testgevallen die een specifieke verweving van draden uitoefenen. Daarom zouden we idealiter in staat moeten zijn om elke significante interleaving in een aparte thread deterministisch te testen.

Nu valt een volledige inleiding tot deze functierijke bibliotheek buiten het bestek van deze zelfstudie. Maar we kunnen zeker zien hoe we snel tests kunnen opzetten die ons de mogelijke interleavings tussen uitvoerende threads opleveren.

Laten we eens kijken hoe we onze code deterministischer kunnen testen met MultithreadedTC:

openbare klasse MyTests breidt MultithreadedTestCase {privé MyCounter-teller uit; @Override public void initialize () {counter = new MyCounter (); } public void thread1 () gooit InterruptedException {counter.increment (); } public void thread2 () gooit InterruptedException {counter.increment (); } @Override public void finish () {assertEquals (2, counter.getCount ()); } @Test public void testCounter () gooit Throwable {TestFramework.runManyTimes (nieuwe MyTests (), 1000); }}

Hier stellen we twee threads in om op de gedeelde teller te werken en deze te verhogen. We hebben MultithreadedTC geconfigureerd om deze test met deze threads uit te voeren voor maximaal duizend verschillende interleavings totdat er een wordt gedetecteerd die mislukt.

4.4. Java jcstress

OpenJDK onderhoudt Code Tool Project om ontwikkelaarstools te bieden voor het werken aan de OpenJDK-projecten. Er zijn verschillende handige tools onder dit project, waaronder de Java Concurrency Stress Tests (jcstress). Dit wordt ontwikkeld als een experimenteel harnas en een reeks tests om de juistheid van gelijktijdigheidsondersteuning in Java te onderzoeken.

Hoewel dit een experimenteel hulpmiddel is, kunnen we dit nog steeds gebruiken om gelijktijdige code te analyseren en tests te schrijven om daarmee verband houdende defecten te financieren. Laten we eens kijken hoe we de code die we tot nu toe hebben gebruikt in deze tutorial kunnen testen. Het concept is redelijk vergelijkbaar vanuit een gebruiksperspectief:

@JCStressTest @Outcome (id = "1", verwacht = ACCEPTABLE_INTERESTING, desc = "Een update verloren.") @Outcome (id = "2", verwacht = ACCEPTABLE, desc = "Beide updates.") @State openbare klasse MyCounterTests {privé MyCounter-teller; @Actor openbare ongeldige actor1 () {counter.increment (); } @Actor openbare ongeldige actor2 () {counter.increment (); } @Arbiter openbare ongeldige arbiter (I_Result r) {r.r1 = counter.getCount (); }}

Hier hebben we de klas gemarkeerd met een annotatie Staat, wat aangeeft dat het gegevens bevat die zijn gemuteerd door meerdere threads. We gebruiken ook een annotatie Acteur, die de methoden markeert die de acties bevatten die door verschillende threads worden uitgevoerd.

Ten slotte hebben we een methode gemarkeerd met een annotatie Arbiter, die in wezen de staat maar één keer bezoekt Acteurs hebben het bezocht. We hebben ook gebruik gemaakt van annotatie Resultaat om onze verwachtingen te definiëren.

Over het algemeen is de installatie vrij eenvoudig en intuïtief te volgen. We kunnen dit uitvoeren met behulp van een testharnas, gegeven door het framework, dat alle klassen vindt die zijn geannoteerd met JCStressTest en voert ze uit in verschillende iteraties om alle mogelijke interleavings te verkrijgen.

5. Andere manieren om problemen met gelijktijdigheid te detecteren

Het schrijven van tests voor gelijktijdige code is moeilijk maar mogelijk. We hebben de uitdagingen gezien en enkele van de populaire manieren om ze te overwinnen. Echter, we zijn mogelijk niet in staat om alle mogelijke problemen met gelijktijdigheid alleen door middel van tests te identificeren - vooral wanneer de incrementele kosten van het schrijven van meer tests zwaarder beginnen te wegen dan de voordelen ervan.

Daarom kunnen we, samen met een redelijk aantal geautomatiseerde tests, andere technieken gebruiken om problemen met gelijktijdigheid te identificeren. Dit vergroot onze kansen om gelijktijdigheidsproblemen te vinden zonder al te veel dieper in te gaan op de complexiteit van geautomatiseerde tests. In deze sectie behandelen we enkele hiervan.

5.1. Statische analyse

Statische analyse verwijst naar de analyse van een programma zonder het daadwerkelijk uit te voeren. Welnu, wat voor goeds kan zo'n analyse doen? Daar komen we op terug, maar laten we eerst begrijpen hoe dit contrasteert met dynamische analyse. De unit-tests die we tot nu toe hebben geschreven, moeten worden uitgevoerd met de daadwerkelijke uitvoering van het programma dat ze testen. Dit is de reden dat ze deel uitmaken van wat we grotendeels dynamische analyse noemen.

Houd er rekening mee dat statische analyse op geen enkele manier een vervanging is voor dynamische analyse. Het biedt echter een hulpmiddel van onschatbare waarde om de codestructuur te onderzoeken en mogelijke defecten te identificeren, lang voordat we de code zelfs maar uitvoeren. De statische analyse maakt gebruik van een groot aantal sjablonen die zijn samengesteld met ervaring en begrijpen.

Hoewel het heel goed mogelijk is om gewoon door de code te kijken en deze te vergelijken met de best practices en regels die we hebben samengesteld, moeten we toegeven dat het niet aannemelijk is voor grotere programma's. Er zijn echter verschillende tools beschikbaar om deze analyse voor ons uit te voeren. Ze zijn redelijk volwassen, met een enorme hoeveelheid regels voor de meeste populaire programmeertalen.

Een veelgebruikte tool voor statische analyse voor Java is FindBugs. FindBugs zoekt naar gevallen van "bugpatronen". Een bugpatroon is een code-idioom dat vrij vaak een fout is. Dit kan verschillende redenen hebben, zoals moeilijke taalkenmerken, verkeerd begrepen methoden en verkeerd begrepen invarianten.

FindBugs inspecteert de Java-bytecode op het voorkomen van bugpatronen zonder de bytecode daadwerkelijk uit te voeren. Dit is best handig in gebruik en snel in gebruik. FindBugs rapporteert bugs die tot veel categorieën behoren, zoals voorwaarden, ontwerp en gedupliceerde code.

Het bevat ook defecten die verband houden met gelijktijdigheid. Er moet echter worden opgemerkt dat FindBugs valse positieven kan rapporteren. Deze zijn in de praktijk minder, maar moeten worden gecorreleerd met handmatige analyse.

5.2. Model controleren

Modelcontrole is een methode om te controleren of een eindige-toestandsmodel van een systeem aan een bepaalde specificatie voldoet. Nu klinkt deze definitie misschien te academisch, maar houd het een tijdje vol!

We kunnen een rekenprobleem doorgaans voorstellen als een eindige-toestandsmachine. Hoewel dit op zichzelf al een enorm gebied is, geeft het ons een model met een eindige reeks toestanden en overgangsregels daartussen met duidelijk gedefinieerde begin- en eindtoestanden.

Nu de specificatie definieert hoe een model zich moet gedragen om als correct te worden beschouwd. In wezen bevat deze specificatie alle vereisten van het systeem dat het model vertegenwoordigt. Een van de manieren om specificaties vast te leggen, is door gebruik te maken van de temporele logische formule, ontwikkeld door Amir Pnueli.

Hoewel het logisch mogelijk is om modelcontrole handmatig uit te voeren, is het vrij onpraktisch. Gelukkig zijn er veel tools beschikbaar om ons hierbij te helpen.Een van deze tools die beschikbaar zijn voor Java is Java PathFinder (JPF). JPF is ontwikkeld met jarenlange ervaring en onderzoek bij NASA.

Specifiek, JPF is een modelchecker voor Java-bytecode. Het draait een programma op alle mogelijke manieren en controleert daarbij op eigendomsovertredingen zoals deadlock en onverwerkte uitzonderingen langs alle mogelijke uitvoeringspaden. Het kan daarom heel nuttig blijken te zijn bij het vinden van defecten die verband houden met gelijktijdigheid in elk programma.

6. Nabeschouwing

Dat zou voor ons inmiddels geen verrassing moeten zijn het is het beste om de complexiteit van multi-threaded code te vermijden zo veel mogelijk. Het ontwikkelen van programma's met eenvoudiger ontwerpen, die gemakkelijker te testen en te onderhouden zijn, zou ons hoofddoel moeten zijn. We moeten het erover eens zijn dat gelijktijdig programmeren vaak nodig is voor moderne toepassingen.

Echter, we kunnen verschillende best practices en principes toepassen bij het ontwikkelen van gelijktijdige programma's dat kan ons leven gemakkelijker maken. In dit gedeelte zullen we enkele van deze praktische tips doornemen, maar we moeten in gedachten houden dat deze lijst verre van compleet is!

6.1. Verminder complexiteit

Complexiteit is een factor die het testen van een programma moeilijk kan maken, zelfs zonder gelijktijdige elementen. Dit wordt alleen maar groter in het licht van gelijktijdigheid. Het is niet moeilijk te begrijpen waarom eenvoudigere en kleinere programma's zijn gemakkelijker te beredeneren en dus effectief te testen. Er zijn verschillende beste patronen die ons hierbij kunnen helpen, zoals SRP (Single Responsibility Pattern) en KISS (Keep It Stupid Simple), om er maar een paar te noemen.

Hoewel deze het probleem van het rechtstreeks schrijven van tests voor gelijktijdige code niet aanpakken, maken ze het werk gemakkelijker om te proberen.

6.2. Overweeg Atomic Operations

Atoomoperaties zijn operaties die volledig onafhankelijk van elkaar verlopen. Daarom kunnen de moeilijkheden bij het voorspellen en testen van interleaving eenvoudig worden vermeden. Compare-and-swap is zo'n veelgebruikte atomaire instructie. Simpel gezegd, het vergelijkt de inhoud van een geheugenlocatie met een bepaalde waarde en wijzigt, alleen als ze hetzelfde zijn, de inhoud van die geheugenlocatie.

De meeste moderne microprocessors bieden een variant op deze instructie. Java biedt een reeks atomaire klassen zoals AtomicInteger en AtomicBoolean, met de voordelen van onderstaande vergelijkings- en ruilinstructies.

6.3. Omarm onveranderlijkheid

Bij multi-threaded programmering laten gedeelde gegevens die kunnen worden gewijzigd altijd ruimte voor fouten. Onveranderlijkheid verwijst naar de toestand waarin een datastructuur na instantiatie niet kan worden gewijzigd. Dit is een match made in heaven voor gelijktijdige programma's. Als de staat van een object niet kan worden gewijzigd nadat het is gemaakt, hoeven concurrerende threads geen wederzijdse uitsluiting op hen aan te vragen. Dit vereenvoudigt het schrijven en testen van gelijktijdige programma's aanzienlijk.

Houd er echter rekening mee dat we misschien niet altijd de vrijheid hebben om voor onveranderlijkheid te kiezen, maar we moeten ervoor kiezen wanneer het mogelijk is.

6.4. Vermijd gedeeld geheugen

De meeste problemen met betrekking tot multi-threaded programmeren kunnen worden toegeschreven aan het feit dat we een gedeeld geheugen hebben tussen concurrerende threads. Wat als we ze gewoon kwijt konden raken! We hebben nog steeds een mechanisme nodig om threads te laten communiceren.

Er zijn alternatieve ontwerppatronen voor gelijktijdige applicaties die ons deze mogelijkheid bieden. Een van de populaire is het Actormodel, dat de actor voorschrijft als de basiseenheid van concurrency. In dit model communiceren actoren met elkaar door berichten te verzenden.

Akka is een in Scala geschreven raamwerk dat gebruikmaakt van het Actormodel om betere gelijktijdigheidsprimitieven te bieden.

7. Conclusie

In deze zelfstudie hebben we enkele basisprincipes behandeld die verband houden met gelijktijdig programmeren. We hebben in het bijzonder in detail de multi-threaded concurrency in Java besproken. Tijdens het testen van dergelijke code hebben we de uitdagingen die het ons biedt doorgenomen, vooral met gedeelde gegevens. Verder hebben we enkele van de tools en technieken doorgenomen die beschikbaar zijn om gelijktijdige code te testen.

We hebben ook andere manieren besproken om problemen met gelijktijdigheid te voorkomen, waaronder tools en technieken naast geautomatiseerde tests. Ten slotte hebben we enkele van de best practices voor programmeren met betrekking tot gelijktijdig programmeren doorgenomen.

De broncode voor dit artikel is te vinden op GitHub.