Tråding av modeller i Java

1. Introduksjon

Ofte i applikasjonene våre trenger vi å kunne gjøre flere ting samtidig. Vi kan oppnå dette på flere måter, men nøkkelen blant dem er å implementere multitasking i noen form.

Multitasking betyr å kjøre flere oppgaver samtidig, hvor hver oppgave utfører sitt arbeid. Disse oppgavene kjører vanligvis alle samtidig, leser og skriver det samme minnet og samhandler med de samme ressursene, men gjør forskjellige ting.

2. Innfødte tråder

Den vanlige måten å implementere multi-tasking på Java er å bruke tråder. Tråding støttes vanligvis ned til operativsystemet. Vi kaller tråder som fungerer på dette nivået "native threads".

Operativsystemet har noen evner med threading som ofte ikke er tilgjengelige for applikasjonene våre, rett og slett på grunn av hvor mye nærmere det er den underliggende maskinvaren. Dette betyr at utføring av innfødte tråder vanligvis er mer effektiv. Disse trådene kartlegges direkte til kjøringstråder på datamaskinens CPU - og operativsystemet styrer kartleggingen av tråder på CPU-kjerner.

Standard threading-modellen i Java, som dekker alle JVM-språk, bruker native threads. Dette har vært tilfelle siden Java 1.2, og er tilfelle uansett det underliggende systemet som JVM kjører på.

Dette betyr at når som helst vi bruker noen av de vanlige trådmekanismene i Java, bruker vi innfødte tråder. Dette inkluderer java.lang.Tråd, java.util.concurrent.Executor, java.util.concurrent.ExecutorService, og så videre.

3. Grønne tråder

I programvareteknikk, et alternativ til innfødte tråder er grønne tråder. Det er her vi bruker tråder, men de tilordnes ikke direkte til operativsystemtråder. I stedet administrerer den underliggende arkitekturen trådene selv og styrer hvordan disse tilordnes til operativsystemtråder.

Vanligvis fungerer dette ved å kjøre flere innfødte tråder og deretter tildele de grønne trådene til disse innfødte trådene for utføring. Systemet kan da velge hvilke grønne tråder som er aktive til enhver tid, og hvilke innfødte tråder de er aktive på.

Dette høres veldig komplisert ut, og det er det. Men det er en komplikasjon som vi generelt ikke trenger å bry oss om. Den underliggende arkitekturen tar seg av alt dette, og vi får bruke den som om det var en innfødt trådmodell.

Så hvorfor skulle vi gjøre dette? Innfødte tråder er veldig effektive å kjøre, men de har høye kostnader rundt å starte og stoppe dem. Grønne tråder bidrar til å unngå denne kostnaden og gir arkitekturen mye mer fleksibilitet. Hvis vi bruker relativt langvarige tråder, er innfødte tråder veldig effektive. For veldig kortvarige jobber kan kostnadene ved å starte dem oppveie fordelen ved å bruke dem. I disse tilfellene kan grønne tråder bli mer effektive.

Dessverre, Java har ikke innebygd støtte for grønne tråder.

Svært tidlige versjoner brukte grønne tråder i stedet for innfødte tråder som standard trådmodell. Dette endret seg i Java 1.2, og det har ikke vært noen støtte for det på JVM-nivå siden.

Det er også utfordrende å implementere grønne tråder i biblioteker, fordi de trenger veldig lavt nivå støtte for å prestere bra. Som sådan er et vanlig alternativ brukt fibre.

4. Fibre

Fibre er en alternativ form for multi-threading og ligner på grønne tråder. I begge tilfeller bruker vi ikke innfødte tråder og bruker i stedet de underliggende systemkontrollene som kjører når som helst. Den store forskjellen mellom grønne tråder og fibre er i nivået på kontroll, og spesifikt hvem som har kontrollen.

Grønne tråder er en form for forebyggende multitasking. Dette betyr at den underliggende arkitekturen er helt ansvarlig for å bestemme hvilke tråder som skal utføres til enhver tid.

Dette betyr at alle de vanlige problemene med threading gjelder, der vi ikke vet noe om rekkefølgen på trådene våre som kjøres, eller hvilke som skal kjøres samtidig. Det betyr også at det underliggende systemet må være i stand til å stoppe og starte koden vår når som helst, potensielt midt i en metode eller til og med en uttalelse.

Fibre er i stedet en form for samarbeidende multitasking, noe som betyr at en løpende tråd vil fortsette å kjøre til den signaliserer at den kan gi til en annen. Det betyr at det er vårt ansvar at fibrene samarbeider med hverandre. Dette gir oss direkte kontroll over når fibrene kan stoppe utførelsen, i stedet for at systemet bestemmer dette for oss.

Dette betyr også at vi må skrive koden vår på en måte som tillater dette. Ellers vil det ikke fungere. Hvis koden vår ikke har noen avbruddspunkter, kan vi like godt ikke bruke fibre i det hele tatt.

Java har foreløpig ikke innebygd støtte for fibre. Det finnes noen biblioteker som kan introdusere dette for applikasjonene våre, inkludert men ikke begrenset til:

4.1. Quasar

Quasar er et Java-bibliotek som fungerer bra med ren Java og Kotlin og har en alternativ versjon som fungerer med Clojure.

Det fungerer ved å ha en Java-agent som trenger å kjøre ved siden av applikasjonen, og denne agenten er ansvarlig for å administrere fibrene og sørge for at de fungerer riktig. Bruken av en Java-agent betyr at det ikke er behov for noen spesielle byggetrinn.

Quasar krever også at Java 11 fungerer riktig, slik at det kan begrense applikasjonene som kan bruke den. Eldre versjoner kan brukes på Java 8, men disse støttes ikke aktivt.

4.2. Kilim

Kilim er et Java-bibliotek som tilbyr veldig lik funksjonalitet til Quasar, men gjør det ved å bruke bytecode-veving i stedet for en Java-agent. Dette betyr at det kan fungere flere steder, men det gjør byggeprosessen mer komplisert.

Kilim fungerer med Java 7 og nyere og fungerer riktig selv i scenarier der en Java-agent ikke er et alternativ. For eksempel hvis en annen allerede brukes til instrumentering eller overvåking.

4.3. Project Loom

Project Loom er et eksperiment fra OpenJDK-prosjektet for å legge til fibre i selve JVM, snarere enn som et tilleggsbibliotek. Dette vil gi oss fordelene med fibre fremfor tråder. Ved å implementere den på JVM direkte, kan det bidra til å unngå komplikasjoner som Java-agenter og bytecode-veving introduserer.

Det er ingen gjeldende utgivelsesplan for Project Loom, men vi kan laste ned binærfiler for tidlig tilgang akkurat nå for å se hvordan det går. Men fordi det fortsatt er veldig tidlig, må vi være forsiktige med å stole på dette for enhver produksjonskode.

5. Co-Routines

Samrutiner er et alternativ til gjenging og fiber. Vi kan tenke på co-rutiner som fibre uten noen form for planlegging. I stedet for at det underliggende systemet bestemmer hvilke oppgaver som skal utføres når som helst, gjør koden vår dette direkte.

Generelt skriver vi co-rutiner slik at de gir på bestemte punkter i flyten. Disse kan sees på som pausepunkter i vår funksjon, der den slutter å virke og potensielt gir noe mellomresultat. Når vi gir etter, blir vi stoppet til ringekoden bestemmer oss for å starte oss på nytt av en eller annen grunn. Dette betyr at ringekoden vår styrer planleggingen av når denne skal kjøre.

Kotlin har innfødt støtte for co-rutiner innebygd i standardbiblioteket. Det er flere andre Java-biblioteker som vi kan bruke til å implementere dem også hvis ønskelig.

6. Konklusjon

Vi har sett flere forskjellige alternativer for multi-tasking i koden vår, alt fra de tradisjonelle innfødte trådene til noen veldig lette alternativer. Hvorfor ikke prøve dem ut neste gang et program trenger samtidighet?


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