Threading-modellen in Java

1. Inleiding

Vaak moeten we in onze applicaties meerdere dingen tegelijk kunnen doen. We kunnen dit op verschillende manieren bereiken, maar de belangrijkste daarvan is om multitasking in een of andere vorm te implementeren.

Multi-tasking betekent dat u meerdere taken tegelijkertijd uitvoert, waar elke taak zijn werk uitvoert. Deze taken worden doorgaans allemaal tegelijkertijd uitgevoerd, waarbij ze hetzelfde geheugen lezen en schrijven en met dezelfde bronnen communiceren, maar verschillende dingen doen.

2. Native Threads

De standaardmanier om multi-tasking in Java te implementeren, is door threads te gebruiken. Threading wordt meestal ondersteund tot op het besturingssysteem. We noemen threads die op dit niveau werken 'native threads'.

Het besturingssysteem heeft een aantal mogelijkheden met threading die vaak niet beschikbaar zijn voor onze applicaties, simpelweg omdat het veel dichter bij de onderliggende hardware staat. Dit betekent dat het uitvoeren van native threads doorgaans efficiënter is. Deze threads worden rechtstreeks toegewezen aan de uitvoeringsdraden op de computer-CPU - en het besturingssysteem beheert de toewijzing van threads op CPU-kernen.

Het standaard threading-model in Java, dat alle JVM-talen dekt, maakt gebruik van native threads. Dit is het geval sinds Java 1.2 en is het geval ongeacht het onderliggende systeem waarop de JVM draait.

Dit betekent dat elke keer dat we een van de standaard threading-mechanismen in Java gebruiken, we native threads gebruiken. Dit bevat java.lang.Thread, java.util.concurrent.Executor, java.util.concurrent.ExecutorService, enzovoorts.

3. Groene draden

In software engineering, een alternatief voor native threads zijn groene threads. Dit is waar we threads gebruiken, maar ze zijn niet direct toegewezen aan threads van het besturingssysteem. In plaats daarvan beheert de onderliggende architectuur de threads zelf en bepaalt hoe deze worden toegewezen aan threads van het besturingssysteem.

Meestal werkt dit door verschillende native threads uit te voeren en vervolgens de groene threads aan deze native threads toe te wijzen voor uitvoering. Het systeem kan vervolgens kiezen welke groene threads op een bepaald moment actief zijn en op welke native threads ze actief zijn.

Dit klinkt erg ingewikkeld, en dat is het ook. Maar het is een complicatie waar we over het algemeen niet om hoeven te geven. De onderliggende architectuur zorgt voor dit alles en we kunnen het gebruiken alsof het een native threading-model is.

Dus waarom zouden we dit doen? Native threads zijn zeer efficiënt om uit te voeren, maar ze hebben hoge kosten bij het starten en stoppen ervan. Groene draden helpen deze kosten te vermijden en geven de architectuur veel meer flexibiliteit. Als we relatief langlopende threads gebruiken, zijn native threads erg efficiënt. Voor banen van zeer korte duur kunnen de kosten om ermee te beginnen opwegen tegen de voordelen van het gebruik ervan. In deze gevallen kunnen groene draden efficiënter worden.

Helaas, Java heeft geen ingebouwde ondersteuning voor groene discussies.

Zeer vroege versies gebruikten groene draden in plaats van native draden als het standaard model voor draadsnijden. Dit veranderde in Java 1.2 en sindsdien is er geen ondersteuning meer voor op JVM-niveau.

Het is ook een uitdaging om groene discussies in bibliotheken te implementeren, omdat ze ondersteuning op zeer laag niveau nodig hebben om goed te presteren. Als zodanig wordt een veelgebruikt alternatief gebruikt: vezels.

4. Vezels

Vezels zijn een alternatieve vorm van multi-threading en lijken op groene draden. In beide gevallen gebruiken we geen native threads, maar gebruiken we de onderliggende systeembesturingen die op elk moment worden uitgevoerd. Het grote verschil tussen groene draden en vezels zit in het niveau van controle, en specifiek wie de controle heeft.

Groene draden zijn een vorm van preventieve multitasking. Dit betekent dat de onderliggende architectuur volledig verantwoordelijk is om te beslissen welke threads op een bepaald moment worden uitgevoerd.

Dit betekent dat alle gebruikelijke problemen van threading van toepassing zijn, waarbij we niets weten over de volgorde waarin onze threads worden uitgevoerd of welke tegelijkertijd worden uitgevoerd. Het betekent ook dat het onderliggende systeem in staat moet zijn om onze code op elk moment te pauzeren en opnieuw te starten, mogelijk midden in een methode of zelfs een statement.

Vezels zijn in plaats daarvan een vorm van coöperatieve multitasking, wat betekent dat een lopende draad blijft lopen totdat deze aangeeft dat hij kan wijken voor een andere. Het betekent dat het onze verantwoordelijkheid is dat de vezels met elkaar samenwerken. Dit geeft ons directe controle over wanneer de vezels de uitvoering kunnen onderbreken, in plaats van dat het systeem dit voor ons beslist.

Dit betekent ook dat we onze code zo moeten schrijven dat dit mogelijk is. Anders werkt het niet. Als onze code geen onderbrekingspunten heeft, kunnen we net zo goed helemaal geen vezels gebruiken.

Java heeft momenteel geen ingebouwde ondersteuning voor vezels. Er zijn enkele bibliotheken die dit in onze applicaties kunnen introduceren, inclusief maar niet beperkt tot:

4.1. Quasar

Quasar is een Java-bibliotheek die goed werkt met pure Java en Kotlin en heeft een alternatieve versie die werkt met Clojure.

Het werkt door een Java-agent te hebben die naast de applicatie moet draaien, en deze agent is verantwoordelijk voor het beheer van de vezels en ervoor te zorgen dat ze correct samenwerken. Het gebruik van een Java-agent betekent dat er geen speciale buildstappen nodig zijn.

Quasar vereist ook dat Java 11 correct werkt, dus dat kan de toepassingen beperken die het kunnen gebruiken. Oudere versies kunnen worden gebruikt op Java 8, maar deze worden niet actief ondersteund.

4.2. Kelim

Kilim is een Java-bibliotheek die zeer vergelijkbare functionaliteit biedt als Quasar, maar dit doet door bytecode-weaving te gebruiken in plaats van een Java-agent. Dit betekent dat het op meer plaatsen kan werken, maar het maakt het bouwproces gecompliceerder.

Kilim werkt met Java 7 en nieuwer en zal correct werken, zelfs in scenario's waarin een Java-agent geen optie is. Bijvoorbeeld als er al een andere wordt gebruikt voor instrumentatie of monitoring.

4.3. Project Weefgetouw

Project Loom is een experiment van het OpenJDK-project om vezels aan de JVM zelf toe te voegen, in plaats van als een add-on-bibliotheek. Dit geeft ons de voordelen van vezels ten opzichte van draden. Door het rechtstreeks op de JVM te implementeren, kan het helpen complicaties te voorkomen die door Java-agents en bytecode-weaving worden geïntroduceerd.

Er is geen actueel releaseschema voor Project Loom, maar we kunnen binaire bestanden met vroege toegang nu downloaden om te zien hoe het gaat. Omdat het echter nog erg vroeg is, moeten we voorzichtig zijn door hierop te vertrouwen voor elke productiecode.

5. Co-routines

Co-routines zijn een alternatief voor draadsnijden en vezels. We kunnen co-routines zien als vezels zonder enige vorm van planning. In plaats van dat het onderliggende systeem beslist welke taken op elk moment worden uitgevoerd, doet onze code dit rechtstreeks.

Over het algemeen schrijven we co-routines zodat ze op specifieke punten van hun stroom meegeven. Deze kunnen worden gezien als pauzepunten in onze functie, waar het stopt met werken en mogelijk een tussenresultaat oplevert. Wanneer we toegeven, worden we gestopt totdat de aanroepcode besluit om ons om welke reden dan ook opnieuw op te starten. Dit betekent dat onze aanroepcode de planning bepaalt wanneer dit wordt uitgevoerd.

Kotlin heeft native ondersteuning voor co-routines die in de standaardbibliotheek is ingebouwd. Er zijn verschillende andere Java-bibliotheken die we kunnen gebruiken om ze desgewenst ook te implementeren.

6. Conclusie

We hebben verschillende alternatieven voor multi-tasking in onze code gezien, variërend van de traditionele native threads tot enkele zeer lichtgewicht alternatieven. Waarom zou u ze de volgende keer niet uitproberen als een toepassing gelijktijdigheid nodig heeft?


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