Hvorfor må lokale variabler som brukes i Lambdas være endelige eller effektive?

1. Introduksjon

Java 8 gir oss lambdas, og ved tilknytning, forestillingen om effektivt endelig variabler. Har du noen gang lurt på hvorfor lokale variabler fanget i lambdas må være endelige eller effektivt endelige?

Vel, JLS gir oss et lite hint når det står "Begrensningen for effektivt endelige variabler forbyr tilgang til dynamisk skiftende lokale variabler, hvis fangst sannsynligvis vil innføre samtidighetsproblemer." Men hva betyr det?

I de neste avsnittene vil vi grave dypere inn i denne begrensningen og se hvorfor Java introduserte den. Vi viser eksempler for å demonstrere hvordan det påvirker applikasjoner med en tråd og samtidig, og vi vil også avslå et vanlig antimønster for å jobbe rundt denne begrensningen.

2. Fange Lambdas

Lambda-uttrykk kan bruke variabler som er definert i et ytre omfang. Vi refererer til disse lambdas som fange lambdas. De kan fange opp statiske variabler, forekomstvariabler og lokale variabler, men bare lokale variabler må være endelige eller effektivt endelige.

I tidligere Java-versjoner kjørte vi inn i dette da en anonym indre klasse fanget en variabel lokal til metoden som omringet den - vi trengte å legge til endelig nøkkelord før den lokale variabelen for at kompilatoren skal være lykkelig.

Som litt syntaktisk sukker, kan nå kompilatoren gjenkjenne situasjoner der, mens endelig nøkkelordet er ikke til stede, referansen endres ikke i det hele tatt, noe som betyr at det er effektivt endelig. Vi kan si at en variabel faktisk er endelig hvis kompilatoren ikke klager hvis vi erklærer den endelig.

3. Lokale variabler i å fange Lambdas

For å si det enkelt, dette vil ikke kompilere:

Leverandørinkrementer (int start) {return () -> start ++; }

start er en lokal variabel, og vi prøver å endre den inne i et lambdauttrykk.

Den grunnleggende grunnen til at dette ikke vil kompilere er at lambda er det fange verdien av start, som betyr å lage en kopi av den. Å tvinge variabelen til å være endelig, unngår å gi inntrykk av å øke start inne i lambda kunne faktisk endre start metodeparameter.

Men hvorfor lager det en kopi? Vel, legg merke til at vi returnerer lambda fra vår metode. Dermed kjøres ikke lambda før etter start metodeparameter samler søppel. Java må lage en kopi av start for at denne lambda skal leve utenfor denne metoden.

3.1. Samtidige problemer

For moro skyld, la oss forestille oss et øyeblikk at Java gjorde la lokale variabler på en eller annen måte forbli koblet til sine fangede verdier.

Hva skal vi gjøre her:

public void localVariableMultithreading () {boolean run = true; executor.execute (() -> {while (run) {// do operation}}); run = false; }

Selv om dette ser uskyldig ut, har det det snikende problemet med "synlighet". Husk at hver tråd får sin egen stabel, og hvordan sørger vi for at vår samtidig som Løkke ser endringen til løpe variabel i den andre stabelen? Svaret i andre sammenhenger kan være å bruke synkronisert blokker eller flyktige nøkkelord.

Derimot, fordi Java pålegger den faktiske endelige begrensningen, trenger vi ikke å bekymre oss for kompleksiteter som dette.

4. Statiske eller forekomstvariabler i å fange Lambdas

Eksemplene før kan reise noen spørsmål hvis vi sammenligner dem med bruk av statiske eller forekomstvariabler i et lambdauttrykk.

Vi kan lage vårt første eksempel bare ved å konvertere vårt start variabel til en forekomstvariabel:

privat int start = 0; Leverandørinkrementer () {return () -> start ++; }

Men hvorfor kan vi endre verdien av start her?

Enkelt sagt, det handler om hvor medlemsvariabler er lagret. Lokale variabler er på bunken, men medlemsvariabler er på bunken. Fordi vi har med heapminne å gjøre, kan kompilatoren garantere at lambda har tilgang til den nyeste verdien av start.

Vi kan fikse vårt andre eksempel ved å gjøre det samme:

privat flyktig boolsk løp = sann; public void instanceVariableMultithreading () {executor.execute (() -> {while (run) {// do operation}}); run = false; }

De løpe variabelen er nå synlig for lambda, selv når den kjøres i en annen tråd siden vi la til flyktige nøkkelord.

Generelt sett, når vi tar en forekomstvariabel, kan vi tenke på den som å fange den endelige variabelen dette. Uansett, det faktum at kompilatoren ikke klager, betyr ikke at vi ikke bør ta forholdsregler, spesielt i flertrådingsmiljøer.

5. Unngå løsninger

For å komme rundt begrensningen på lokale variabler, kan noen tenke på å bruke variabelleiere for å endre verdien av en lokal variabel.

La oss se et eksempel som bruker en matrise til å lagre en variabel i en applikasjon med en tråd:

public int workaroundSingleThread () {int [] holder = new int [] {2}; IntStream sums = IntStream .of (1, 2, 3) .map (val -> val + holder [0]); holder [0] = 0; retur sums.sum (); }

Vi kunne tro at strømmen summerer 2 til hver verdi, men det summerer faktisk 0 siden dette er den siste verdien som er tilgjengelig når lambda kjøres.

La oss gå et skritt videre og utføre summen i en annen tråd:

public void workaroundMultithreading () {int [] holder = new int [] {2}; Runnable runnable = () -> System.out.println (IntStream .of (1, 2, 3) .map (val -> val + holder [0]) .sum ()); ny tråd (kjørbar) .start (); // simulere litt behandling prøv {Thread.sleep (new Random (). nextInt (3) * 1000L); } fange (InterruptedException e) {throw new RuntimeException (e); } holder [0] = 0; }

Hvilken verdi summerer vi her? Det avhenger av hvor lang tid den simulerte behandlingen tar. Hvis det er kort nok til at utførelsen av metoden avsluttes før den andre tråden utføres, vil den skrive ut 6, ellers vil den skrive ut 12.

Generelt er denne typen løsninger feilutsatt og kan gi uforutsigbare resultater, så vi bør alltid unngå dem.

6. Konklusjon

I denne artikkelen har vi forklart hvorfor lambdauttrykk bare kan bruke endelige eller effektive endelige lokale variabler. Som vi har sett, kommer denne begrensningen fra forskjellige variabler og hvordan Java lagrer dem i minnet. Vi har også vist farene ved å bruke en vanlig løsning.

Som alltid er hele kildekoden for eksemplene tilgjengelig på GitHub.


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