OpenJDK Project Loom

1. Overzicht

In dit artikel gaan we kort in op Project Loom. In essentie, het primaire doel van Project Loom is het ondersteunen van een lichtgewicht gelijktijdigheidsmodel met hoge doorvoer in Java.

2. Project Weefgetouw

Project Loom is een poging van de OpenJDK-gemeenschap om een ​​lichtgewicht concurrency-constructie in Java te introduceren. De prototypes voor Loom hebben tot nu toe een verandering in de JVM en de Java-bibliotheek geïntroduceerd.

Hoewel er nog geen geplande release voor Loom is, hebben we toegang tot de recente prototypes op de wiki van Project Loom.

Voordat we de verschillende concepten van Loom bespreken, bespreken we eerst het huidige concurrency-model in Java.

3. Java's gelijktijdigheidsmodel

Momenteel, Draad vertegenwoordigt de kern abstractie van concurrency in Java. Deze abstractie, samen met andere gelijktijdige API's, maakt het gemakkelijk om gelijktijdige toepassingen te schrijven.

Aangezien Java echter de OS-kernelthreads gebruikt voor de implementatie, voldoet het niet aan de huidige eis van gelijktijdigheid. Er zijn met name twee grote problemen:

  1. Draden kan niet overeenkomen met de schaal van de eenheid van gelijktijdigheid van het domein. Toepassingen staan ​​bijvoorbeeld meestal tot miljoenen transacties, gebruikers of sessies toe. Het aantal threads dat door de kernel wordt ondersteund, is echter veel minder. Dus een Thread voor elke gebruiker, transactie of sessie is vaak niet haalbaar.
  2. De meeste gelijktijdige toepassingen hebben voor elk verzoek enige synchronisatie tussen threads nodig. Hierdoor, er vindt een dure contextwisseling plaats tussen OS-threads.

Een mogelijke oplossing voor dergelijke problemen is het gebruik van asynchrone gelijktijdige API's. Bekende voorbeelden zijn CompletableFuture en RxJava. Op voorwaarde dat dergelijke API's de kernel-thread niet blokkeren, geeft het een applicatie een fijnere concurrency-constructie bovenop Java-threads.

Aan de andere kant, dergelijke API's zijn moeilijker te debuggen en te integreren met verouderde API's. En daardoor is er behoefte aan een lichtgewicht concurrency-constructie die onafhankelijk is van kerneldraden.

4. Taken en planners

Elke implementatie van een thread, lichtgewicht of zwaar, hangt af van twee constructies:

  1. Taak (ook bekend als een voortzetting) - Een reeks instructies die zichzelf kan opschorten voor een blokkering
  2. Scheduler - Voor het toewijzen van de voortzetting aan de CPU en het opnieuw toewijzen van de CPU vanuit een gepauzeerde voortzetting

Momenteel, Java vertrouwt op OS-implementaties voor zowel de voortzetting als de planner.

Om een ​​voortzetting op te schorten, is het nu nodig om de volledige call-stack op te slaan. En haal op dezelfde manier de call-stack op bij hervatting. Aangezien de OS-implementatie van voortzettingen de native call-stack samen met de call-stack van Java omvat, resulteert dit in een zware footprint.

Een groter probleem is echter het gebruik van de OS-planner. Omdat de planner in kernelmodus draait, is er geen onderscheid tussen threads. En het behandelt elk CPU-verzoek op dezelfde manier.

Dit type planning is niet optimaal voor met name Java-toepassingen.

Beschouw bijvoorbeeld een toepassingsthread die een actie uitvoert op de verzoeken en vervolgens de gegevens doorgeeft aan een andere thread voor verdere verwerking. Hier, het zou beter zijn om beide threads op dezelfde CPU te plannen. Maar aangezien de planner agnostisch is voor de thread die de CPU aanvraagt, is dit onmogelijk te garanderen.

Project Loom stelt voor om dit op te lossen via threads in gebruikersmodus die afhankelijk zijn van Java-runtime-implementatie van voortzettingen en planners in plaats van de OS-implementatie.

5. Vezels

In de recente prototypes in OpenJDK is een nieuwe klasse genaamd Vezel wordt geïntroduceerd in de bibliotheek naast het Draad klasse.

Omdat de geplande bibliotheek voor Vezels is gelijkaardig aan Draad, moet de gebruikersimplementatie ook vergelijkbaar blijven. Er zijn echter twee belangrijke verschillen:

  1. Vezel zou verpak elke taak in een voortzetting van de interne gebruikersmodus. Hierdoor kan de taak worden onderbroken en hervat in Java-runtime in plaats van in de kernel
  2. Een pluggable user-mode scheduler (VorkJoinPool, bijvoorbeeld) zou worden gebruikt

Laten we deze twee items in detail bekijken.

6. Voortzettingen

Een voortzetting (of co-routine) is een reeks instructies die de beller kan opgeven en in een later stadium kan hervatten.

Elk vervolg heeft een ingangspunt en een vloeipunt. Het vloeipunt is waar het werd opgehangen. Telkens wanneer de beller het vervolg hervat, keert de besturing terug naar het laatste punt van opbrengst.

Het is belangrijk om te beseffen dat dit onderbreken / hervatten nu plaatsvindt in de taalruntime in plaats van in het besturingssysteem. Daarom voorkomt het de dure contextwisseling tussen kernelthreads.

Net als bij draden, streeft Project Loom ernaar om geneste vezels te ondersteunen. Omdat vezels intern afhankelijk zijn van voortzettingen, moeten deze ook geneste voortzettingen ondersteunen. Overweeg een les om dit beter te begrijpen Voortzetting waarmee nesten mogelijk is:

Continuation cont1 = new Continuation (() -> {Continuation cont2 = new Continuation (() -> {// doe iets opschorten (SCOPE_CONT_2); suspend (SCOPE_CONT_1);});});

Zoals hierboven getoond, kan de geneste voortzetting zichzelf of een van de omsluitende voortzettingen opschorten door een bereikvariabele door te geven. Om deze reden, ze staan ​​bekend als bereik voortzettingen.

Aangezien het opschorten van een voortzetting ook vereist dat de call-stack wordt opgeslagen, is het ook een doel van project Loom om lichtgewicht stack-retrieval toe te voegen terwijl de voortzetting wordt hervat.

7. Planner

Eerder bespraken we de tekortkomingen van de OS-planner bij het plannen van relateerbare threads op dezelfde CPU.

Hoewel het een doel is van Project Loom om pluggable schedulers met vezels mogelijk te maken, ForkJoinPool in asynchrone modus wordt gebruikt als de standaardplanner.

ForkJoinPool werkt aan de algoritme voor het stelen van werk. Elke thread houdt dus een taakverdeling bij en voert de taak vanaf zijn hoofd uit. Bovendien blokkeert elke inactieve thread niet, wachtend op de taak en trekt het in plaats daarvan uit de staart van een andere draad.

Het enige verschil in asynchrone modus is dat de werkdraden stelen de taak van het hoofd van een ander deque.

ForkJoinPool voegt een taak toe die is gepland door een andere actieve taak aan de lokale wachtrij. Vandaar dat het op dezelfde CPU wordt uitgevoerd.

8. Conclusie

In dit artikel bespraken we de problemen in Java's huidige concurrency-model en de wijzigingen die zijn voorgesteld door Project Loom.

Daarbij hebben we ook taken en planners gedefinieerd en gekeken hoe Fibers en ForkJoinPool zouden een alternatief kunnen bieden voor Java met behulp van kernelthreads.